> ## Documentation Index
> Fetch the complete documentation index at: https://docs.polymorfa.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Send a follow-up after delivery or read

> Send one follow-up message from the same number, timed from when the first message was delivered or read. Replies and opt-outs cancel it.

A campaign can send each recipient one follow-up message. The wait starts when
WhatsApp reports the first message as delivered or read, and the follow-up
comes from the number that sent the first message. A recipient who replies or
opts out never gets it.

<Note>
  Follow-ups are a beta. When the beta is offered to your team, a team owner or
  admin selects **Join** on the Receipt-timed campaign follow-ups beta row in the
  campaign builder's
  message step, reviews the terms and selects **Accept and join**. Until your
  team has joined and the beta is available to it, requests that set a
  follow-up, or launch or copy a campaign that has one, answer
  `403 feature_unavailable`. Removing a follow-up from a draft always works.
</Note>

## How the timing works

* **Delivery starts the wait.** The follow-up is due its delay after WhatsApp
  first reports the first message as delivered.
* **A read takes precedence.** If WhatsApp reports the first message as read
  before the follow-up starts sending, the wait restarts from that first read.
  Later reads do not move it again.
* **No receipt, no follow-up.** A follow-up never sends on a timer alone.
  Recipients who turned off read receipts report deliveries only, so their wait
  starts from delivery.
* **Expiry never sends.** The expiry counts from when the first message was
  sent. A follow-up that is not sent by then is marked `expired` and never sent.

| Setting | Default | Allowed range |
| - | - | - |
| `delaySeconds` | 86,400 (1 day) | 60 to 604,800 (7 days) |
| `expirySeconds` | 604,800 (7 days) | 86,400 to 2,592,000 (30 days), and longer than the delay |

The campaign's [send window](/guides/campaigns/send-a-campaign) also applies:
a follow-up that falls due outside the window waits for the next opening, as
long as it has not expired.

## What cancels a follow-up

* The recipient replies to the number that sent the first message.
* The phone number is added to your opt-out list, whether by a STOP keyword,
  the API, an import or the console. A later START does not restore a
  cancelled follow-up.
* You stop the campaign.
* The sending number changes: it is renamed, connected again as a new
  connection, or switches sending type. A follow-up is only ever sent from the
  original number and connection.
* Your team leaves the beta, or the beta is turned off.

Pausing a campaign holds follow-ups. Their waits keep counting, so a follow-up
whose expiry passes while the campaign is paused expires without sending.
While the sending number is offline or disabled, the follow-up waits until it
returns or the follow-up expires.

A follow-up that has started sending is never recalled or sent twice. If its
result is unknown, for example after a connection drop, Polymorfa reconciles it
from the original send and reports `outcome_unknown` until it can prove the
outcome.

## Requirements

* The campaign sends one first message: a single composed message or a
  template. A campaign with a sequence of composed messages cannot launch with
  a follow-up.
* The follow-up is either text (1 to 16,384 characters, with the same
  `{{variable}}` placeholders as the campaign) or a message template
  (`templateId`). Follow-up text cannot contain tracked links
  (`{{link:key}}`).
* On a Cloud API number, the follow-up follows the same 24-hour window rule as
  any composed message: outside it, a text follow-up fails. Use an approved
  template to reach recipients outside that window.
* The follow-up fills its variables the same way as the first message,
  including the campaign's variable mapping. A recipient without a value for a
  variable that has no inline fallback does not get the follow-up; it ends as
  `failed` with reason `missing_variable`. The console accepts only variables
  that the first message or its mapping already uses.
* You can add, change or remove the follow-up only while the campaign is an
  unlaunched draft. Launch freezes it.
* A/B tests and weighted variations work with follow-ups: each recipient's own
  first message starts their wait.

Waiting, receipts and cancellations are free. Each follow-up that is sent is a
campaign message, with the same funding, plan and safety checks as the first
message.

## Add a follow-up

### Use the console

In the campaign builder's message step, turn on **Send one follow-up**, write
the message or choose a message template, and set how long to wait and when
to stop waiting. The review step shows the follow-up before you launch.

### Use the API

Send `followUp` when you create or update a draft:

```bash theme={null}
curl -X PATCH "https://api.polymorfa.com/messaging/projects/$PROJECT_SLUG/campaigns/$CAMPAIGN_ID" \
  -H "Authorization: Bearer $POLYMORFA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "followUp": {
      "delaySeconds": 86400,
      "expirySeconds": 604800,
      "message": { "type": "text", "text": "Hi {{first_name}}, still interested? Reply YES to hold your size." }
    }
  }'
```

To send a message template instead, use
`"message": { "type": "template", "templateId": "<template UUID>" }`. Send
`"followUp": null` to remove the follow-up. On the Platform API, send the same
field to `POST /platform/campaigns` or `PATCH /platform/campaigns/{campaignId}`.

A follow-up the API refuses answers `400 invalid_parameter` with
`param: "followUp"`. Launching a campaign that combines a follow-up with a
message sequence answers `409 state_conflict`.

## Read follow-up results

Follow-up results are counted apart from the first message. The first-message
counts and webhooks keep their meaning.

### Campaign analytics

`GET /messaging/projects/{projectSlug}/campaigns/{id}/analytics` and
`GET /platform/campaigns/{campaignId}/analytics` return a `followUps` object:

| Field | Meaning |
| - | - |
| `total` | Recipients with a follow-up |
| `waiting` | Waiting for a delivery or read receipt |
| `scheduled` | Timed from a receipt and waiting to send |
| `sending` | Sending now |
| `sent` | Sent |
| `delivered`, `read` | Sent follow-ups WhatsApp reported as delivered or read |
| `replied` | Sent follow-ups the recipient replied to |
| `cancelled`, `expired`, `failed` | Ended without sending |
| `unknown` | Started sending, outcome not yet proven |
| `nextDueAt` | Earliest scheduled send, in Unix milliseconds, or null |

All counts are zero for a campaign without a follow-up.

### Recipients

Each recipient from `GET /messaging/projects/{projectSlug}/campaigns/{id}/recipients`
or `GET /platform/campaigns/{campaignId}/recipients` has a `followUp` object,
or null when no follow-up was created for that recipient. It includes
`status`, `anchorKind` (`delivered` or `read`), `firstDeliveredAt`,
`firstReadAt`, `dueAt`, `expiresAt`, `sentAt`, `deliveredAt`, `readAt`,
`repliedAt`, `reason` and `commandId`. Times are Unix milliseconds.

`status` is one of `waiting_receipt`, `scheduled`, `sending`, `sent`,
`cancelled`, `expired`, `failed` or `outcome_unknown`. `reason` explains a
cancellation, expiry, failure or wait, for example `reply`, `opt_out`,
`wait_expired`, `campaign_stop`, `sender_changed`, `feature_withdrawn`,
`send_window`, `sender_unavailable` or `missing_variable`.

The recipient CSV export adds the columns `followUpStatus`, `followUpAnchor`,
`followUpDueAt`, `followUpExpiresAt`, `followUpSentAt`, `followUpReadAt`,
`followUpRepliedAt`, `followUpReason` and `followUpCommandId`.

### Campaign history

`GET /platform/campaigns/{campaignId}/events` records each follow-up state
change as a `followup.<status>` event, such as `followup.scheduled` or
`followup.cancelled`, with the recipient, the sending number and the reason.
It never contains message text or phone numbers. The same state changes
appear in the Console's [Logs](/console/audit-logs) as `campaign.followup.<status>`
rows. Follow-ups do not add webhook events.

## Leaving the beta

Leaving the beta, or the beta being turned off, cancels follow-ups that have
not started sending. Sends that already started finish and are never sent
again. Joining again later does not restore cancelled follow-ups. Results stay
readable, and you can still remove the follow-up from a draft.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.