Drafts

Preparing and scheduling Messages for your agents.

What is a Draft?

A Draft is an unsent Message. It’s a resource that allows your agent to prepare the contents of an email—including recipients, a subject, a body, and Attachments—without sending it immediately.

We know agent reliability is big these days—with Drafts you can have agents have ready-to-send emails and only with your permission it can send them off into the world.

Drafts are a key component for building advanced agent workflows. They enable:

  • Human-in-the-Loop Review: An agent can create a Draft for a sensitive or important Message, which a human can then review and approve before it’s sent.
  • Scheduled Sending: Your agent can create a Draft and then have a separate process send it at a specific time, such as during business hours for the recipient.
  • Complex Composition: For Messages that require multiple steps to build (e.g., fetching data from several sources, generating content), Drafts allow you to save the state of the email as it’s being composed.

The Draft Lifecycle

You can interact with Drafts throughout their lifecycle, from creation to the moment they are sent.

1. Create a Draft

This is the first step. You create a Draft in a specific Inbox that will eventually be the sender.

1# You'll need an inbox ID to create a draft in.
2
3new_draft=client.inboxes.drafts.create(
4 inbox_id="outbound@domain.com",
5 to=["review-team@example.com"],
6 subject="[NEEDS REVIEW] Agent's proposed response"
7)
8
9print(f"Draft created successfully with ID: {new_draft.draft_id}")

2. Get Draft

Once a Draft is created, you can retrieve it by its ID

1# Get the draft
2draft = client.inboxes.drafts.get(inbox_id = “my_inbox@domain.com”, draft_id = “draft_id_123”)

3. Send a Draft

This is the final step that converts the Draft into a sent Message. Once sent, the Draft is deleted.

1# This sends the draft and deletes it
2
3sent_message = client.inboxes.drafts.send(inbox_id = 'my_inbox@domain.com', draft_id = 'draft_id_123')
4
5print(f"Draft sent! New message ID: {sent_message.message_id}")

Note that now we access it by message_id now because now its a message!!

Scheduled Sending

You can schedule a Draft to be sent automatically at a future time by passing the send_at field when creating or updating a Draft. AgentMail will automatically send it at the specified time—no cron jobs or polling required.

Schedule a Draft

Pass an ISO 8601 datetime string to send_at. The Draft will be automatically labeled scheduled and its send_status will be set to scheduled.

1from datetime import datetime, timedelta
2
3# Schedule an email for tomorrow at 9:00 AM UTC
4send_time = (datetime.utcnow() + timedelta(days=1)).replace(
5 hour=9, minute=0, second=0
6)
7
8scheduled_draft = client.inboxes.drafts.create(
9 inbox_id="outreach@domain.com",
10 to=["prospect@example.com"],
11 subject="Following up on our conversation",
12 text="Hi, just wanted to follow up on our chat yesterday...",
13 send_at=send_time.isoformat() + "Z"
14)
15
16print(f"Draft scheduled for {scheduled_draft.send_at}")
17# send_status will be "scheduled"

Cancel or Reschedule

To cancel a scheduled send, delete the Draft. To reschedule, update send_at with a new time.

1# Reschedule to a different time
2new_time = (datetime.utcnow() + timedelta(days=3)).replace(hour=14, minute=0, second=0)
3client.inboxes.drafts.update(
4 inbox_id="outreach@domain.com",
5 draft_id=scheduled_draft.draft_id,
6 send_at=new_time.isoformat() + "Z"
7)
8
9# Or cancel by deleting the draft entirely
10client.inboxes.drafts.delete(
11 inbox_id="outreach@domain.com",
12 draft_id=scheduled_draft.draft_id
13)

List Scheduled Drafts

When a Draft is created with send_at, it is automatically labeled scheduled. You can filter for scheduled drafts using the labels query parameter.

1# List all scheduled drafts in an inbox
2scheduled = client.inboxes.drafts.list(
3 inbox_id="outreach@domain.com",
4 labels=["scheduled"]
5)
6
7for draft in scheduled.drafts:
8 print(f"{draft.subject} — scheduled for {draft.send_at} ({draft.send_status})")
send_status Values
  • scheduled — The draft is queued and will be sent at the send_at time.
  • sending — The draft is currently being processed for delivery.
  • failed — The send attempt failed. You can retry by updating send_at to a new time.

Conditional Follow-Ups

A common pattern is “send a follow-up in 3 days, but only if they haven’t replied.” You can implement this by scheduling a follow-up Draft, then cancelling it via Webhook if a reply arrives.

1. Send the initial email and schedule the follow-up:

1from datetime import datetime, timedelta
2
3inbox_id = "outreach@domain.com"
4
5# Send initial email
6initial = client.inboxes.messages.send(
7 inbox_id=inbox_id,
8 to=["prospect@example.com"],
9 subject="Quick question about your workflow",
10 text="Hi, I noticed your team is scaling quickly..."
11)
12
13# Schedule follow-up for 3 days later
14follow_up_time = (datetime.utcnow() + timedelta(days=3)).replace(hour=9, minute=0, second=0)
15
16follow_up = client.inboxes.drafts.create(
17 inbox_id=inbox_id,
18 to=["prospect@example.com"],
19 subject="Re: Quick question about your workflow",
20 text="Hi again — just bumping this in case it got buried...",
21 in_reply_to=initial.message_id,
22 send_at=follow_up_time.isoformat() + "Z"
23)
24
25# Tag the thread so your webhook handler can find the draft
26client.inboxes.threads.update(
27 inbox_id=inbox_id,
28 thread_id=initial.thread_id,
29 add_labels=[f"follow-up:{follow_up.draft_id}"]
30)

2. Cancel on reply via webhook:

When a reply comes in, look for the follow-up:<draft_id> label on the thread and delete the draft.

1# In your webhook handler for "message.received":
2thread = client.inboxes.threads.get(inbox_id=inbox_id, thread_id=thread_id)
3
4for label in thread.labels:
5 if label.startswith("follow-up:"):
6 draft_id = label.split("follow-up:")[1]
7 try:
8 client.inboxes.drafts.delete(inbox_id=inbox_id, draft_id=draft_id)
9 except Exception:
10 pass # Draft may have already been sent
11 client.inboxes.threads.update(
12 inbox_id=inbox_id, thread_id=thread_id,
13 remove_labels=[label]
14 )
15 break

If the prospect replies, the follow-up is cancelled. If they don’t, it sends automatically at the scheduled time.

Org-Wide Draft Management

Similar to Threads, you can list all Drafts across your entire Organization. This is perfect for building a central dashboard where a human supervisor can view, approve, or delete any Draft created by any agent in your fleet.

1# Get all drafts across the entire organization
2all_drafts = client.drafts.list()
3
4print(f"Found {all_drafts.count} drafts pending review.")

Copy for Cursor / Claude

Copy one of the blocks below into Cursor or Claude for complete Drafts API knowledge in one shot.

1"""
2AgentMail Drafts — copy into Cursor/Claude.
3
4Setup: pip install agentmail python-dotenv. Set AGENTMAIL_API_KEY in .env.
5
6API reference:
7- inboxes.drafts.create(inbox_id, to, subject?, text?, html?, cc?, bcc?, reply_to?, attachments?, send_at?)
8- inboxes.drafts.get(inbox_id, draft_id)
9- inboxes.drafts.update(inbox_id, draft_id, to?, subject?, text?, html?, send_at?, ...)
10- inboxes.drafts.send(inbox_id, draft_id) — converts to Message, deletes draft
11- inboxes.drafts.delete(inbox_id, draft_id)
12- inboxes.drafts.list(inbox_id, limit?, page_token?, labels?)
13- drafts.list(limit?, page_token?) — org-wide
14
15Scheduled sending: pass send_at (ISO 8601 datetime) to create() or update().
16Draft is auto-labeled 'scheduled' and sent at the specified time.
17send_status: 'scheduled' | 'sending' | 'failed'.
18Cancel by deleting the draft. Reschedule by updating send_at.
19
20Errors: SDK raises on 4xx/5xx. Rate limit: 429 with Retry-After.
21"""
22import os
23from datetime import datetime, timedelta
24from dotenv import load_dotenv
25from agentmail import AgentMail
26
27load_dotenv()
28client = AgentMail(api_key=os.getenv("AGENTMAIL_API_KEY"))
29
30inbox_id = "agent@agentmail.to"
31
32# Create and send immediately
33draft = client.inboxes.drafts.create(inbox_id, to=["review@example.com"], subject="[REVIEW] Proposed reply")
34sent = client.inboxes.drafts.send(inbox_id, draft.draft_id)
35print(sent.message_id)
36
37# Schedule for later
38send_time = (datetime.utcnow() + timedelta(days=1)).replace(hour=9, minute=0, second=0)
39scheduled = client.inboxes.drafts.create(
40 inbox_id, to=["prospect@example.com"], subject="Follow up",
41 text="Just following up...", send_at=send_time.isoformat() + "Z"
42)
43print(f"Scheduled for {scheduled.send_at}, status: {scheduled.send_status}")
44
45all_drafts = client.drafts.list()