# Scheduled email delivery

Scheduled email delivery lets Lettermint accept an email now and release it later. You can schedule a single email, schedule each item in a batch, or use an SMTP header.

Scheduled email delivery is available to **Growth** and **Pro** teams. Free and Starter teams can continue to send emails immediately.

## Schedule limits

The scheduled time must be from 30 seconds through 30 days after Lettermint receives the request. Both limits are inclusive.

Lettermint accepts these time formats:

| Format | Example |
| --- | --- |
| ISO 8601 with UTC | `2026-08-25T07:00:00Z` |
| ISO 8601 with an offset | `2026-08-25T09:00:00+02:00` |
| Relative English time | `in 1 hour` |
| English date and time | `tomorrow at 9am` |
| English time with a timezone | `Friday at 3pm ET` |

Lettermint uses the request receipt time for a relative value. It uses UTC when an English value does not contain a timezone. It converts every accepted value to UTC.

For a batch request, Lettermint uses one receipt time for all items. Each item can have a different scheduled time.

:::note
The scheduled time is the time when Lettermint starts the delivery process. It is not a guarantee that the recipient will receive the email at that exact time.
:::

## Schedule with the Sending API

Add `scheduled_at` to a send request:

```bash
curl -X POST "https://api.lettermint.co/v1/send" \
  -H "x-lettermint-token: $LETTERMINT_PROJECT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "sender@yourdomain.com",
    "to": ["recipient@example.com"],
    "subject": "Appointment reminder",
    "html": "<p>Your appointment starts tomorrow.</p>",
    "scheduled_at": "tomorrow at 9am"
  }'
```

Lettermint returns `202 Accepted` for a scheduled email:

```json
{
  "message_id": "0198df4e-3351-7da3-b01d-a8c77b696f96",
  "status": "scheduled",
  "scheduled_at": "2026-08-25T09:00:00+00:00"
}
```

The response contains the normalized UTC time. An immediate send keeps the current immediate-delivery response and behavior.

An `X-LM-Scheduled-At` value in the API `headers` object does not schedule an email. Use `scheduled_at` for an API request.

## Schedule a batch

Add `scheduled_at` to each item that you want to schedule:

```bash
curl -X POST "https://api.lettermint.co/v1/send/batch" \
  -H "x-lettermint-token: $LETTERMINT_PROJECT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[
    {
      "from": "sender@yourdomain.com",
      "to": ["first@example.com"],
      "subject": "First reminder",
      "text": "Your first reminder.",
      "scheduled_at": "2026-08-25T09:00:00+02:00"
    },
    {
      "from": "sender@yourdomain.com",
      "to": ["second@example.com"],
      "subject": "Second reminder",
      "text": "Your second reminder.",
      "scheduled_at": "in 2 hours"
    }
  ]'
```

Each batch item is independent. Items without `scheduled_at` use immediate delivery.

## Schedule with SMTP

Add exactly one `X-LM-Scheduled-At` header to an authenticated outbound SMTP message:

```text
X-LM-Scheduled-At: 2026-08-25T09:00:00+02:00
```

You can also use an English value:

```text
X-LM-Scheduled-At: Friday at 3pm ET
```

The header name is not case-sensitive. Lettermint rejects an empty or duplicate header, including duplicates that use different letter casing. Lettermint removes the control header before it stores customer headers or delivers the email.

Only authenticated outbound SMTP accepts this control. Use a Project API token as the SMTP password. After acceptance, the SMTP `250 2.0.0` response contains the normalized UTC schedule.

See [Send email with SMTP](/guides/send-email-with-smtp) for connection settings and authentication.

## Change a scheduled time

Use the dashboard, or send a `PATCH` request with only `scheduled_at`:

```bash
curl -X PATCH \
  "https://api.lettermint.co/v1/messages/0198df4e-3351-7da3-b01d-a8c77b696f96" \
  -H "x-lettermint-token: $LETTERMINT_PROJECT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "scheduled_at": "tomorrow at 3pm"
  }'
```

The same time returns `200 OK` and does not create a second reschedule event.

You can change the time only when no release worker has an active lease. A canceled email cannot be scheduled again.

## Cancel a scheduled email

Use the dashboard, or send a cancel request:

```bash
curl -X POST \
  "https://api.lettermint.co/v1/messages/0198df4e-3351-7da3-b01d-a8c77b696f96/cancel" \
  -H "x-lettermint-token: $LETTERMINT_PROJECT_TOKEN"
```

Cancellation is final. A repeated cancellation returns `200 OK` and does not create a second cancellation event.

## Dashboard and events

The message status is `scheduled` before release and `canceled` after cancellation. The dashboard shows the requested time in local time and UTC. It also shows a safe failure code when a permanent check fails.

Scheduled email events are also available in the timeline and as webhooks:

- `message.scheduled`
- `message.rescheduled`
- `message.canceled`
- `message.released`

## Credential checks at release

Lettermint checks the original Project API token again at release. The release fails with `credential_inactive` when the token was deleted, revoked, or no longer has the required access. Token expiration after schedule acceptance does not stop the release.

Lettermint also checks the current team, project, route, domain, Return-Path, and recipient restrictions before release.

## Quota reservations

Lettermint reserves email quota when it accepts a scheduled email. Immediate sends and mixed batches include these reservations in their capacity checks. This prevents a team with a hard cap or a spend limit from accepting more email than its limit permits.

At release, Lettermint converts the reservation to submitted usage. It does not run a new quota capacity check. A cancellation or a permanent failure before release removes the reservation.

## Idempotent retries

Use an [idempotency key](/platform/emails/idempotency) when you create a schedule. A retry with the same key and request body returns the original message ID and normalized schedule. Lettermint does not parse a relative value again for a matching idempotent retry.

## Errors

### Sending API errors

| HTTP response | Code | Cause |
| --- | --- | --- |
| `403` | `FEATURE_NOT_AVAILABLE` | The team plan does not include scheduled email delivery. |
| `409` | `SCHEDULE_CONFLICT` | The current lifecycle state does not permit the action. |
| `422` | `INVALID_SCHEDULE` | The time is invalid, too early, or too late. |

### SMTP errors

| SMTP response | Cause | Retry |
| --- | --- | --- |
| `550 5.6.0` | The header is invalid, empty, duplicated, too early, or too late. | Do not retry. |
| `550 5.7.1` | The plan does not include the feature, or the credential is not permitted. | Do not retry. |
| `550` | The idempotency key conflicts with different content. | Do not retry. |
| `451 4.3.0` | A temporary service, database, storage, or broker failure occurred. | Retry later. |

## Related documentation

- [Sending API](/api-reference/sending/send)
- [Send a batch](/api-reference/sending/send#send-multiple-emails-in-a-batch)
- [Send email with SMTP](/guides/send-email-with-smtp)
- [Idempotency](/platform/emails/idempotency)
