# Drafts

> A Pidgeon message not sent yet: composing it over several calls, attaching files to it, and sending it now or at a time you choose.

A draft is a message that has not been sent. An agent that writes a reply,
waits for a person to approve it, and sends it an hour later is ordinary
supervised automation — and this is where the hour in the middle lives.

```bash
curl https://pidgeon.ai/api/v1/drafts \
-H "Authorization: Bearer $PIDGEON_API_KEY" \
-H "Content-Type: application/json" \
-d '{
  "identity_id": "0c939bd7-2d69-4150-a2ac-de994f0e4887",
  "to": "priya@example.com",
  "subject": "Re: Invoice 4471",
  "text": "Reissued, thank you."
}'
```

```ts
const { data } = await draftsCreate({
body: {
  identity_id: '0c939bd7-2d69-4150-a2ac-de994f0e4887',
  to: ['priya@example.com'],
  subject: 'Re: Invoice 4471',
  text: 'Reissued, thank you.',
},
});
```

```python
from pidgeon.api.drafts import drafts_create

draft = drafts_create.sync(client=client, body=body)
```

**Only `identity_id` is required.** A draft with nothing else in it is a
legitimate thing to create — it is what a composer does when somebody opens it
— and the rest arrives by `PATCH` as they type.

## Changing one

`PATCH` keeps what you leave out, so saving as somebody types is a body with
the field that changed:

```sh
PATCH /v1/drafts/48532ec7-db7a-4437-ada6-c55132a3d735   { "text": "Reissued yesterday, thank you." }
```

## Sending it

```sh
POST /v1/drafts/48532ec7-db7a-4437-ada6-c55132a3d735/send
```

What is stored is what goes. There is no body, because a draft that needed
arguments at the moment of sending would be a message you had not finished
writing.

Sending answers with the message, and the draft is gone — it became the thing
it was for.

## Sending it later

```bash
curl https://pidgeon.ai/api/v1/drafts/48532ec7-db7a-4437-ada6-c55132a3d735/schedule \
-H "Authorization: Bearer $PIDGEON_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "send_at": "2026-11-04T09:00:00Z" }'
```

```ts
await draftsSchedule({
path: { id: '48532ec7-db7a-4437-ada6-c55132a3d735' },
body: { send_at: '2026-11-04T09:00:00Z' },
});
```

```python
from pidgeon.api.drafts import drafts_schedule

drafts_schedule.sync(client=client, id="48532ec7-db7a-4437-ada6-c55132a3d735", body=body)
```

A minute from now at the earliest and thirty days at the latest. `DELETE` on
the same path takes the schedule off and leaves an ordinary draft.

**A refused send unschedules rather than retrying.** If the address cannot
write to that recipient when the time comes — a send policy, a suppression —
the draft comes back to you with the reason, instead of a job trying again
every hour until somebody notices.

## Attaching a file

Upload it first, then name it on the draft. [Attachments](https://pidgeon.ai/docs/attachments.md)
has the upload; `attachment_ids` is where it goes, and
`DELETE /v1/drafts/{id}/attachments/{attachmentId}` takes one off again.
