# Schedule and cancel emails

> Send an email later with scheduled_at, change the send time, or cancel a scheduled, accepted or retrying email from the API or the dashboard.

This page explains how to schedule an email for later with the Email API, how to move it to a different time, and how to cancel an email before it leaves. Canceling also works for emails that weren't scheduled, as long as they haven't been delivered yet.

## Schedule an email

Add `scheduled_at` to a [send request](/docs/email-api/send-email/). The response has `"status": "scheduled"` and the normalized time in `scheduled_at`, and each recipient's email emits [`email.scheduled`](/docs/webhooks/events/email/scheduled/) instead of `email.accepted`.

`scheduled_at` accepts these formats:

| Format | Example | Notes |
| --- | --- | --- |
| ISO 8601 with a time zone | `2026-10-05T09:00:00Z`, `2026-10-05T09:00:00+02:00` | Recommended. Always include `Z` or an offset. |
| Natural language | `tomorrow at 9am`, `in 2 hours`, `next monday 10:00`, `friday 5pm` | Interpreted in UTC, so `tomorrow at 9am` means 09:00 UTC. |

A time that is now or in the past sends the email immediately with the status `accepted`.

> **Check the response status:** If Emailit can't read the `scheduled_at` value, it doesn't reject the request: the email is sent right away. Check that the response has `"status": "scheduled"` and the `scheduled_at` you expected. Unix timestamps aren't recognized; convert them to ISO 8601 first.

**cURL**

```bash
curl https://api.emailit.com/v2/emails \
  -H "Authorization: Bearer $EMAILIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Acme <reminders@acme.com>",
    "to": "ada@example.com",
    "subject": "Your appointment is tomorrow",
    "text": "See you at 14:00.",
    "scheduled_at": "2026-10-05T09:00:00Z"
  }'
```

**Node.js**

```javascript
const email = await emailit.emails.send({
  from: 'Acme <reminders@acme.com>',
  to: 'ada@example.com',
  subject: 'Your appointment is tomorrow',
  text: 'See you at 14:00.',
  scheduled_at: '2026-10-05T09:00:00Z',
});
```

**Python**

```python
email = client.emails.send({
    "from": "Acme <reminders@acme.com>",
    "to": "ada@example.com",
    "subject": "Your appointment is tomorrow",
    "text": "See you at 14:00.",
    "scheduled_at": "2026-10-05T09:00:00Z",
})
```

**PHP**

```php
$email = $emailit->emails()->send([
    'from' => 'Acme <reminders@acme.com>',
    'to' => 'ada@example.com',
    'subject' => 'Your appointment is tomorrow',
    'text' => 'See you at 14:00.',
    'scheduled_at' => '2026-10-05T09:00:00Z',
]);
```

Emailit prepares a scheduled email when you make the request, not at the send time. The template is rendered, URL attachments are downloaded and credits are charged up front. To change the content, cancel the email and send a new one.

## Change the send time

Use [Update a scheduled email](/docs/api-reference/emails/update/) (`POST /emails/{id}`) with a new `scheduled_at`. The same formats are accepted.

- The email's status must be `scheduled`.
- Its current send time must be more than 3 minutes away.
- The new send time must be more than 3 minutes in the future.

**cURL**

```bash
curl https://api.emailit.com/v2/emails/em_33VtK8mRq1xZp7LwN4cY2bHsDfa \
  -H "Authorization: Bearer $EMAILIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "scheduled_at": "2026-10-05T15:00:00Z" }'
```

**Node.js**

```javascript
await emailit.emails.update('em_33VtK8mRq1xZp7LwN4cY2bHsDfa', {
  scheduled_at: '2026-10-05T15:00:00Z',
});
```

**Python**

```python
client.emails.update("em_33VtK8mRq1xZp7LwN4cY2bHsDfa", {
    "scheduled_at": "2026-10-05T15:00:00Z",
})
```

**PHP**

```php
$emailit->emails()->update('em_33VtK8mRq1xZp7LwN4cY2bHsDfa', [
    'scheduled_at' => '2026-10-05T15:00:00Z',
]);
```

```json
{
  "object": "email",
  "id": "em_33VtK8mRq1xZp7LwN4cY2bHsDfa",
  "status": "scheduled",
  "scheduled_at": "2026-10-05T15:00:00.000Z",
  "updated_at": "2026-10-01T10:02:44.193027Z",
  "message": "Email schedule has been updated successfully"
}
```

Unlike a new send, an unreadable time is rejected here with `422 Invalid scheduled_at`. A request that breaks the 3-minute rule, or targets an email that isn't scheduled, fails with `422 Cannot update email`. A request with several recipients creates one email per recipient, so reschedule each `em_` ID from the `ids` map. Rescheduling isn't available in the dashboard.

## Cancel an email

You can cancel an outgoing email while it has one of these statuses:

| Status | Can you cancel? | Notes |
| --- | --- | --- |
| `scheduled` | Yes | Only while the send time is more than 3 minutes away. |
| `accepted` | Yes, best effort | The email is waiting in the send queue or about to leave it. |
| `attempted` | Yes, best effort | A delivery attempt failed temporarily. Canceling stops the remaining retries. |
| Any other status | No | Emails that were delivered, bounced, failed, rejected, suppressed, held, or already canceled can't be canceled. |

**Dashboard**

  1. Go to **Email API → Emails**.
  2. Select **Cancel delivery** on the email's row, or open the email and select **Cancel delivery** at the top of the page.
  3. Confirm. If a delivery attempt had already started, the dashboard warns you that the attempt may still complete and that the remaining retries were stopped.

**API**

  Call [Cancel an email](/docs/api-reference/emails/cancel/) (`POST /emails/{id}/cancel`). It works with **Full Access** and **Sending Only** keys.

```bash
curl -X POST https://api.emailit.com/v2/emails/em_33VtK8mRq1xZp7LwN4cY2bHsDfa/cancel \
  -H "Authorization: Bearer $EMAILIT_API_KEY"
```

```json
{
  "object": "email",
  "id": "em_33VtK8mRq1xZp7LwN4cY2bHsDfa",
  "status": "canceled",
  "in_flight": false,
  "message": "Email has been canceled and removed from the send queue."
}
```

When `in_flight` is `true`, the email was canceled but a delivery attempt may already be underway, and the message reads "The current delivery attempt may still complete; remaining retries were stopped." A status that can't be canceled, or a scheduled email less than 3 minutes from its send time, returns `422 Cannot cancel email`.

Canceling doesn't refund the credits charged when the email was sent through the API.

### How canceling works

Canceling pulls the email out of the send queue. It isn't a recall from the recipient's inbox.

1. Emailit checks that the email can still be canceled.
2. It sets the status to `canceled`, adds a "Canceled" entry to the email's delivery history and removes it from the send queue.
3. If a delivery worker has already picked the email up, the worker checks the status again right before handing the message to the recipient's server and skips it when it sees `canceled`.
4. Emailit emits `email.canceled` with the `previous_status`.

If the message was already on its way to the recipient's server, that attempt can still succeed. Emailit keeps the status `canceled` even if the racing attempt is delivered or bounces, but the recipient may still get the message. Treat canceling as "stop this before it leaves", not "unsend".

## Statuses and events

| Moment | Status | Webhook event |
| --- | --- | --- |
| Request with a future `scheduled_at` | `scheduled` | [`email.scheduled`](/docs/webhooks/events/email/scheduled/) |
| Send time arrives | `delivered`, `attempted`, `bounced` and so on | The matching delivery event, such as [`email.delivered`](/docs/webhooks/events/email/delivered/) |
| Canceled | `canceled` | `email.canceled`, with `status` and `previous_status` |

A scheduled email doesn't emit `email.accepted` when its send time arrives. See [Email statuses](/docs/logs/email-statuses/) for the full list.

## Related

- [Update a scheduled email](/docs/api-reference/emails/update/)
- [Cancel an email](/docs/api-reference/emails/cancel/)
- [Send an email](/docs/email-api/send-email/)
- [Email statuses](/docs/logs/email-statuses/)
- [Why is my email stuck in Accepted or Scheduled?](/docs/kb/email-stuck-in-scheduled-or-accepted/)

---
Source: https://emailit.com/docs/email-api/scheduling/
