Figment.so
Mac hostingWebsite hostingGuidesHow to usePricing

Turn Selected iMessage Threads into CRM Updates

iMessage threads carry participant handles, conversation text, and message direction — the same fields a CRM needs to log a client interaction. With a connected Mac and the messages:read scope, your app can fetch selected threads through the Figment API, map their fields to your CRM's schema, and hold the result in a staging queue until a person or rules engine approves each record before it touches your contact data.

This guide covers the data model, a concrete field-mapping table, and the review-before-write workflow. For connection and consent setup, see OAuth for iCloud: Connect Apple Data to Your App.

Design guide: The examples below are illustrative and have not been executed against a live account. Verify field names and response shapes against a live connection before writing any records to production.

What the API returns

The API exposes two resource types: chats (conversation threads) and messages (individual events within a thread).

Chat items include:

  • guid — a stable thread identifier. One-on-one threads use iMessage;-;+15551234567; group threads use iMessage;+;<uuid>.
  • displayName — the group thread name when set; absent on most 1:1 threads.

Message items include:

  • guid — stable message identifier
  • text — plain-text body
  • isFromMe — true for sent messages, false for received
  • chatGuid — the parent thread's guid

Two things to check before a full import. First, history on a freshly connected Mac may be incomplete while sync is running — check the historyComplete flag in every list response before treating a pull as a complete record. Second, non-ready connections reject API operations; use the account screen to confirm service status before fetching.

Map iMessage fields to your CRM schema

This table is the core artifact of an iMessage-CRM workflow. Adapt field names to your CRM; the transformations apply regardless of whether you target Salesforce, HubSpot, or a custom system.

iMessage fieldAPI locationCRM fieldTransformation
Thread guidchats[].guidContact lookup keyFor 1:1 threads, parse the handle from the guid suffix — iMessage;-;+15551234567 → +15551234567 — and normalize to E.164. Group guids contain a UUID; use displayName or a manual rule for those.
displayNamechats[].displayNameSubject / deal hintUseful for named group threads tied to a project or deal; absent on most 1:1 threads.
textmessages[].textActivity note bodyNull-check; strip trailing whitespace; truncate to your CRM's character limit.
isFromMemessages[].isFromMeActivity directiontrue → outbound; false → inbound.
chatGuidmessages[].chatGuidThread referenceConfirm the message's chatGuid matches the thread being processed when you merge across fetches.

The API does not document a timestamp field in message items. If your live connection returns additional date fields, verify the field name and unit before writing any date value to production. Do not depend on undocumented fields in a production pipeline.

Build the review-before-write workflow

Writing directly from API output to a CRM introduces two predictable failures: a handle matches more than one contact record, or a thread you didn't intend to sync gets pulled in. A staging step catches both before they create data problems.

Five-stage pattern:

  1. Fetch and map — pull the selected chatGuid values and transform fields using the table above.
  2. Write to a staging store — append mapped records to a pending queue: a database table, a spreadsheet, or a lightweight list you control.
  3. Surface for review — display each staged record with the proposed CRM contact match and the note body.
  4. Approve or discard — a person or a rules engine marks each record approved or rejected with a reason code.
  5. Commit approved records — POST to your CRM API; archive rejections with their reason for audit.

The review step is exactly where you catch the ambiguous case: +15551234567 resolves to two contacts in your CRM. Without a staging queue, you write to whichever record appears first. With it, a person makes the call and the reason is logged.

If your workflow eventually adds outbound iMessages, the same staged, reviewed payload also works with the idempotency key pattern required by the send endpoint — preventing duplicates when a record is committed more than once.

Fetch and filter threads

The messages:read scope covers listing chats, fetching message bodies, and searching by keyword — no separate scope is needed for search.

import os
import requests

BASE_URL      = "https://figment.so/api/apple-connect"
TOKEN         = os.environ["FIGMENT_ACCESS_TOKEN"]   # opaque Figment OAuth token
CONNECTION_ID = os.environ["FIGMENT_CONNECTION_ID"]  # e.g. con_abc123

HEADERS = {"Authorization": f"Bearer {TOKEN}"}

# Step 1 — list recent chats; follow hasMore / nextOffset for full pagination
resp = requests.get(
    f"{BASE_URL}/v1/connections/{CONNECTION_ID}/chats",
    params={"limit": 20, "offset": 0},
    headers=HEADERS,
)
resp.raise_for_status()
body  = resp.json()
chats = body["data"]   # may be empty; check body["hasMore"] and body["nextOffset"]

# Step 2 — keep only threads whose 1:1 handle is tracked in your CRM
CRM_HANDLES = {"+15551234567", "+15559876543"}

def handle_from_guid(guid: str) -> str | None:
    """Return the E.164 handle for a 1:1 thread guid, or None for group threads."""
    parts = guid.split(";")
    if len(parts) == 3 and parts[1] == "-":
        return parts[2]
    return None  # group thread — requires a separate association strategy

selected = [c for c in chats if handle_from_guid(c["guid"]) in CRM_HANDLES]

# Step 3 — fetch messages per thread and build a staging list
STAGED = []

for chat in selected:
    chat_guid = chat["guid"]
    offset    = 0

    while True:
        msg_resp = requests.get(
            f"{BASE_URL}/v1/connections/{CONNECTION_ID}/messages",
            params={"chatGuid": chat_guid, "limit": 50, "offset": offset},
            headers=HEADERS,
        )
        msg_resp.raise_for_status()
        msg_body = msg_resp.json()

        for m in msg_body["data"]:
            STAGED.append({
                "lookup_handle": handle_from_guid(chat_guid),
                "direction":     "outbound" if m["isFromMe"] else "inbound",
                "body":          (m.get("text") or "").strip(),
                "source_guid":   m["guid"],
                "_status":       "pending",
            })

        if not msg_body.get("hasMore"):
            break
        offset = msg_body["nextOffset"]

For keyword-filtered activity — every thread that mentions a contract number, say — replace the per-chat fetch with the search endpoint. Use a URL builder to encode query values; a raw + in a query string decodes as a space, and chatGuid values contain semicolons and plus signs that must be percent-encoded:

GET /v1/connections/{connection_id}/messages/search
    ?query=renewal
    &chatGuid=iMessage%3B-%3B%2B15551234567
    &limit=20

For a deeper tutorial covering reads, pagination, and threading, see Read and Search iMessages with Python.

SMS and RCS threads

Use the service and conversation identifiers returned by your connector to classify messages. Per Apple's forwarding setup guide, Messages in iCloud syncs SMS, MMS, RCS, and iMessage history across all signed-in devices. When Messages in iCloud is active, forwarding is built in and a separate Text Message Forwarding switch is not required. The iPhone must be on, connected to a network, signed into the same Apple Account, and able to send and receive messages.

The same field-mapping table applies once SMS threads appear in the chat list: guid extraction, E.164 normalization, and direction mapping work identically across prefix types.


For the full scope of messages:read and messages:send — including agent-driven replies and search — see iMessage API for Search, Replies, and Agent Actions. For the idempotency key pattern that prevents duplicate sends when a staged record is committed more than once, see Send an iMessage from Node.js Without Duplicates.

Connect your Apple Account to start pulling selected threads into your CRM pipeline.


Subscribe for Figma design and website publishing guides.


Figment.so

Contact

Twitter

Website pricing

Compute index

About

Privacy

Terms