Figment.so
Mac hostingWebsite hostingGuidesHow to usePricing

Track Promises and Follow-Ups in iMessage

iMessage has no built-in "awaiting reply" flag. A promise sent on Monday scrolls out of view by Tuesday, and high-volume threads bury single-line commitments completely. The fix is a small state machine you run against your message history: classify every promise-bearing message, check each one for a resolution signal, and send a follow-up only when the commitment is still open. Figment's iMessage API gives you the search, history, and send endpoints to build exactly that.

Design guide. The endpoint shapes below reflect the Figment iMessage REST interface. Use them against your own configured, approved connection after completing setup.

Why follow-ups vanish in iMessage

Email clients give you flags, snooze, and "awaiting reply" filters. iMessage gives you a scrolling conversation. There is no native concept of thread status, no unresolved-action badge, and no way to star a specific message across contacts without a third-party tool.

The result is predictable: you send "Can you confirm the quote by Friday?", get pulled into another conversation, and Friday passes silently. The original message is still there — it is just several hundred messages deep and invisible unless you search for it.

Programmatic follow-up tracking changes the loop. You search your message history on a schedule, classify what you find, and act on the classification.

The commitment state table

Each row is a state in a simple finite state machine. Your code assigns a state to every tracked message, identified by its guid from the API response, and re-evaluates it on each poll cycle. Store those guid values in your own database alongside the conversation's chatGuid and your deadline timestamp.

StateWhat it meansSignal phrasesResolution checkAction
SentCommitment dispatched; no reply yet"Can you…", "Please send…", "Let me know if…", "Will you…"Has any message with isFromMe: false appeared after this guid in the thread?Move to Awaiting on next poll
AwaitingReply window open; no response detected(same thread, no inbound reply)Re-fetch thread and re-checkWait; escalate to Lapsed after your deadline
AmbiguousA reply arrived but resolution is unclear"Maybe", "I'll try", "possibly", "working on it"Re-check on next poll; optionally run a classifierHold; do not send a follow-up; check again at next interval
ResolvedCommitment confirmed by a clear reply"Done", "Sent", "Yes", "Confirmed", "Here it is"Mark closed; stop pollingLog and archive
LapsedYour deadline passed with no resolution(no Resolved or Ambiguous reply by deadline)Has a manual override been set?Trigger follow-up send with a persisted idempotency key

Scanning for open promises

Search message history for commitment language using the search endpoint. The messages:read scope covers both search and thread history — no separate search scope is needed. Build the URL with a proper URL encoder; a raw + character in a query string decodes as a space:

GET https://figment.so/api/apple-connect/v1/connections/con_xxxx/messages/search
  ?query=can%20you%20confirm
  &limit=50
Authorization: Bearer ACCESS_TOKEN

Run a separate query for each phrase pattern that matters — "please%20send", "let%20me%20know", "will%20you". The response envelope is {data:[...], mode, hasMore, nextOffset, historyComplete, scope}. Paginate using hasMore and nextOffset; do not guess a sentinel count.

Once you have candidate messages, fetch thread context for each chatGuid:

GET https://figment.so/api/apple-connect/v1/connections/con_xxxx/messages
  ?chatGuid=iMessage%3B-%3B%2B15550001234
  &limit=50
Authorization: Bearer ACCESS_TOKEN

Each message item includes a guid, text, isFromMe, and chatGuid. Locate your tracked message by its stored guid, then check whether any subsequent item with isFromMe: false appears after it in the returned list. That ordering is your resolution signal — the API does not guarantee a sortable timestamp field on every item.

If historyComplete is false, earlier messages may still be syncing from iCloud. Treat any gap as Awaiting, not Resolved.

Scope and readiness. If your app grant restricts to selected conversations, include chatGuid in every request. Before scanning, confirm the connection is ready: GET /v1/connections/con_xxxx returns connection.services.messages.state; handle non-2xx responses and use the account screen for setup status. A non-ready connection returns an error, not stale data.

Sending the follow-up without duplicates

When a message reaches Lapsed, your system sends a follow-up. Before making the HTTP call, persist three things in durable storage: the idempotency key, the exact message body, and the target chatGuid. If the process restarts, you need all three to reconcile or safely retry.

Use POST /v1/connections/con_xxxx/messages/send with the Idempotency-Key header:

POST https://figment.so/api/apple-connect/v1/connections/con_xxxx/messages/send
Authorization: Bearer ACCESS_TOKEN
Idempotency-Key: followup-{source_guid}-attempt-1
Content-Type: application/json

{
  "chatGuid": "{chatGuid_from_GET_chats}",
  "text": "Hey — just checking in on the quote I asked about Friday. Still good to confirm?"
}

Use chatGuid values returned by the chat-list endpoint, not manually assembled strings. The messages:send scope is separate from messages:read, and your send grant must include that specific existing conversation.

After an unknown outcome, do not issue a new attempt number. Poll the original receipt using the same key:

GET https://figment.so/api/apple-connect/v1/connections/con_xxxx/messages/receipt
Authorization: Bearer ACCESS_TOKEN
Idempotency-Key: followup-{source_guid}-attempt-1

The receipt data.state distinguishes submitted, delivered, read, failed, and unknown. A failed receipt is a recorded result — repeating the same key returns the same recorded failure; it does not retry the send. Only assign a new attempt number after you have confirmed the prior attempt failed and you are intentionally making a fresh send. Never resend an unknown action under a new key; poll the same key until the state settles.

Sync prerequisites that change your results

Your scan results depend on what the Mac has available. Two settings matter:

Messages in iCloud. When enabled, Apple keeps your entire message history — iMessage, SMS, MMS, and RCS — in sync across devices. Enable it on iPhone under Settings → Apps → Messages (iOS 18 and later; use Settings → Messages on earlier iOS), and on Mac in Messages → Settings → iMessage. When the setting is off, the Mac only has messages received or sent while it was running.

iPhone availability. For SMS, MMS, and RCS threads, the iPhone must be on, online, and signed into the same Apple Account. When Messages in iCloud is enabled, forwarding to the Mac is handled automatically — a separate Text Message Forwarding switch is not always required. (Apple support, July 2026)

Initial sync after a new account connection can leave history incomplete. Build your state machine to tolerate gaps: a missing reply during sync is Awaiting, not Resolved.

Get started

The Figment iMessage REST API — search, history, send, and receipt — is available to developers with an approved account and completed connection setup. Live connections cost $29/month per active connection; sandbox access is free and returns synthetic data. The developer app never receives or stores the Apple password.

To configure a connection and request messages:read and messages:send scopes, start at Connect Apple Account. For the full authentication, pagination, and scope reference, see iMessage API for Search, Replies, and Agent Actions.


Related guides


Subscribe for Figma design and website publishing guides.


Figment.so

Contact

Twitter

Website pricing

Compute index

About

Privacy

Terms