Skip to content

StartOutreach with review

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.