Search the docs
Find a page, a section, or an endpoint.
Drafts
A message not sent yet, and sending it later.
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.
curl https://pidgeon.ai/api/v1/drafts \
-H "Authorization: Bearer $PIDGEON_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"identity_id": "idn_4f2a1",
"to": "priya@example.com",
"subject": "Re: Invoice 4471",
"text": "Reissued, thank you."
}'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:
PATCH /v1/drafts/dft_91ca { "text": "Reissued yesterday, thank you." }Sending it
POST /v1/drafts/dft_91ca/sendWhat 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
curl https://pidgeon.ai/api/v1/drafts/dft_91ca/schedule \
-H "Authorization: Bearer $PIDGEON_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "send_at": "2026-11-04T09:00:00Z" }'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
has the upload; attachment_ids is where it goes, and
DELETE /v1/drafts/{id}/attachments/{attachmentId} takes one off again.