# Track Promises and Follow-Ups in iMessage

Canonical: https://figment.so/blog/imessage-follow-up-tracking

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.

| State | What it means | Signal phrases | Resolution check | Action |
|---|---|---|---|---|
| **Sent** | Commitment 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 |
| **Awaiting** | Reply window open; no response detected | *(same thread, no inbound reply)* | Re-fetch thread and re-check | Wait; escalate to **Lapsed** after your deadline |
| **Ambiguous** | A reply arrived but resolution is unclear | "Maybe", "I'll try", "possibly", "working on it" | Re-check on next poll; optionally run a classifier | Hold; do not send a follow-up; check again at next interval |
| **Resolved** | Commitment confirmed by a clear reply | "Done", "Sent", "Yes", "Confirmed", "Here it is" | Mark closed; stop polling | Log and archive |
| **Lapsed** | Your 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](/apple-connect/account) 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:

```http
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:

```http
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](https://support.apple.com/en-us/102545))

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](/apple-connect/developers). For the full authentication, pagination, and scope reference, see [iMessage API for Search, Replies, and Agent Actions](/imessage-api).

---

**Related guides**

- [OAuth for iCloud: Connect Apple Data to Your App](/oauth-for-icloud)
- [iMessage API for Search, Replies, and Agent Actions](/imessage-api)
- [Read and Search iMessages with Python](/blog/read-imessages-python)
- [Send an iMessage from Node.js Without Duplicates](/blog/send-imessage-nodejs)
