Outreach with review
Your code or agent writes one email per person and sends them in one request. Each email waits in Drafts until a person reads and sends it. A few days later, one call lists who has not replied.
Last updated
Use a key that holds outside email
Create an API key with messages:send and threads:read, and turn on Hold email to outside addresses for review. With this key, an email to anyone outside your own domains is saved as a draft instead of being sent. Email to your own domains still goes out at once.
Send the batch
Send up to 50 emails with POST /v1/messages/batch. Each item is a normal send. Give each one:
client_reference: your ID for the person, such as a CRM record. Sending the same reference again returns the first draft, so a retry never makes a second one. Once the person sent it, you get the sent message instead.metadata: a few values you want back later, such as the campaign.track_reply: true: once the email is sent, the conversation waits for an answer.
curl -X POST https://shipmail.to/api/v1/messages/batch \
-H "Authorization: Bearer $SHIPMAIL_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: q4-outreach-1" \
-d '{
"messages": [
{
"from": "sales@example.com",
"to": ["ada@client.example"],
"subject": "Quick question",
"text": "Hi Ada, ...",
"client_reference": "lead-42",
"metadata": { "campaign": "q4" },
"track_reply": true
},
{
"from": "sales@example.com",
"to": ["grace@other.example"],
"subject": "Quick question",
"text": "Hi Grace, ...",
"client_reference": "lead-43",
"metadata": { "campaign": "q4" },
"track_reply": true
}
]
}'The response has a batch_id and one result per email, in order. A held_message means the email waits in Drafts. An error result says why that one email was refused; the others still went through.
{
"object": "message_batch",
"batch_id": "msgb_abc123",
"data": [
{
"object": "held_message",
"status": "held_for_review",
"draft_id": "Mabc123",
"mailbox_id": "0b6f8d2e-3c41-4a8e-9f1d-6a2b5c7e9d10",
"external_recipients": ["ada@client.example"],
"client_reference": "lead-42",
"batch_id": "msgb_abc123"
},
{
"object": "error",
"index": 1,
"status": 422,
"error": {
"type": "validation_error",
"message": "Validation failed.",
"request_id": "req_123"
}
}
]
}Review and send the drafts
A person opens each draft in the Shipmail inbox or the mobile app, edits it if needed, and sends it. The sent email keeps the client_reference, metadata and batch_id, and the conversation moves to Waiting because of track_reply. A draft nobody sends is never sent.
List who has not replied
Ask for the batch's conversations still waiting on the other person since before a date. You do not need a mailbox_id: the list covers every mailbox the key can read.
curl "https://shipmail.to/api/v1/threads?batch_id=msgb_abc123&attention_state=waiting_on_them&before=2026-11-05T00:00:00Z" \
-H "Authorization: Bearer $SHIPMAIL_API_KEY"Each thread has an attention_state and the time it got there in attention_since. A reply moves the conversation to needs_reply, so it drops out of this list. See the Threads API for every state and filter.