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

# Message Lifecycle

> All message states, transitions, drop reasons, and the executor pipeline from queue to delivery

Every message in Trackly SMS passes through a series of states from creation to final delivery (or failure). Understanding these states helps you troubleshoot delivery issues and build reliable integrations.

## State Diagram

```mermaid theme={null}
stateDiagram-v2
    [*] --> queued: API send / Schedule trigger
    queued --> sent: Claimed by executor
    sent --> delivered: Provider confirms delivery
    sent --> undelivered: Provider reports failure
    sent --> failed: Hard failure
    queued --> dropped: Stale / blocked
    queued --> orphaned: Executor crashed
```

## Message States

| State         | Terminal? | Description                                                                        |
| ------------- | --------- | ---------------------------------------------------------------------------------- |
| `queued`      | No        | Message is in the send queue, waiting for the executor to claim it                 |
| `sent`        | No        | Executor submitted the message to the SMS provider                                 |
| `delivered`   | Yes       | Provider confirmed delivery to the recipient's handset                             |
| `undelivered` | Yes       | Provider attempted delivery but the carrier reported failure                       |
| `failed`      | Yes       | Hard failure — provider rejected the message or a system error occurred            |
| `dropped`     | Yes       | Message was removed from the queue before sending (see drop reasons below)         |
| `orphaned`    | Yes       | Message had no valid sending list — removed from queue and recorded to dead-letter |

## Drop Reasons

When a message is dropped, the `status_detail` field contains the reason.

| Reason                   | Description                                                                           |
| ------------------------ | ------------------------------------------------------------------------------------- |
| `stale`                  | Message sat in the queue longer than 30 minutes and was discarded                     |
| `blocked_content`        | Message body matched a prohibited content filter (crypto, phishing, bank names)       |
| `contact_blocked`        | Recipient is unsubscribed or blocked on the sending list                              |
| `inactive_list`          | The sending list is no longer in `active` status                                      |
| `billing_expired`        | Account billing is invalid and the message exceeded the 60-minute retry window        |
| `webhook_not_configured` | BYOC (bring-your-own-carrier) list requires a webhook endpoint but none is configured |
| `orphan`                 | Message had no `sending_list` reference — likely a data integrity issue               |

## Executor Pipeline

The message executor processes queued messages through a 10-step pipeline.

1. **Poll queue** — Coordinator queries `QueuedTextMessage` for unclaimed messages older than 10 seconds, sorted by send time, in batches of up to 50,000
2. **Group by list** — Messages are grouped by `sending_list` for per-list processing and rate limiting
3. **Validate list** — Check that the sending list exists and is in `active` status; drop messages for inactive or missing lists
4. **Billing preflight** — Verify the account has valid billing (payment method, no payment failures, free tier limit not exceeded); back off retryable messages 5 minutes or drop after 60-minute TTL
5. **Claim messages** — Atomically claim a batch of messages using a unique claim token to prevent duplicate processing across VMs
6. **Filter stale** — Discard messages queued longer than 30 minutes (`STALE_THRESHOLD_MINUTES`)
7. **Filter blocked content** — Run message body through the blocked words checker; drop matches with `BLOCKED_CONTENT` status
8. **Filter blocked contacts** — Check recipients against `ListContactBlock` (unsubscribed/blocked contacts); drop matches with `CONTACT_BLOCKED` status
9. **Send via provider** — Submit messages to the list's SMS provider in batches with per-list rate limiting (default 17,000 messages/min)
10. **Record results** — Insert `RawMessage` records, delete from queue, update `MessageSent` status, and track billing (segments sent per account)

## Webhook Events

Provider delivery reports update message status asynchronously after sending.

| Message State | Webhook Event         | Description                           |
| ------------- | --------------------- | ------------------------------------- |
| `delivered`   | `message.delivered`   | Carrier confirmed delivery to handset |
| `undelivered` | `message.undelivered` | Carrier reported delivery failure     |
| `failed`      | `message.failed`      | Provider rejected the message         |

<Note>
  The `sent` state is set immediately when the provider accepts the message. Final delivery status arrives via webhook, typically within seconds.
</Note>

## Timing

| Threshold             | Value      | Description                                                                |
| --------------------- | ---------- | -------------------------------------------------------------------------- |
| Queue pickup delay    | 10 seconds | Messages must be at least 10 seconds old before the executor picks them up |
| Stale message cutoff  | 30 minutes | Messages in the queue longer than this are dropped as stale                |
| Billing retry backoff | 5 minutes  | Messages for accounts with billing issues are retried after 5 minutes      |
| Billing block TTL     | 60 minutes | After 60 minutes of billing failure, messages are permanently dropped      |
| Stale claim recovery  | 10 minutes | Claims held by crashed workers are released after 10 minutes               |
| Cache refresh         | 2 minutes  | Incremental cache refresh for accounts, lists, and billing configs         |
| Full cache reload     | 1 hour     | Complete cache rebuild as a safety net                                     |

## Next Steps

<CardGroup cols={2}>
  <Card title="Data Model" icon="diagram-project" href="/concepts/data-model">
    Entity relationships
  </Card>

  <Card title="Compliance" icon="shield" href="/concepts/compliance">
    Opt-out handling and TCPA
  </Card>
</CardGroup>
