API v1Private beta. The API is stable; send limits are low while the shared domain warms up.

Concepts

Messages & threads

What send0 extracts from every message, and how conversations are grouped.

The message object

Every inbound message is parsed when it arrives. The fields agents use most:

{
  "object": "message",
  "id": "msg_9Kd2…",
  "thread_id": "thr_3Lp8…",
  "direction": "in",
  "status": "received",
  "expired": false,
  "from": { "name": "Dana Rivera", "email": "dana@acme.com" },
  "subject": "Re: PO #4471 delivery date",
  "extracted_text": "Can you confirm Thursday instead?",
  "extracted": { "otp": null, "links": ["https://acme.com/po/4471"], "action_link": null },
  "auth": { "spf": "pass", "dkim": "pass", "dmarc": "pass" },
  "safety": { "prompt_injection": "none", "reasons": [] },
  "attachments": [
    {
      "id": "att_x1…",
      "filename": "po.pdf",
      "content_type": "application/pdf",
      "size": 84211,
      "inline": false,
      "content_id": null
    }
  ],
  "tag": null
}
FieldWhat it is
text / htmlThe full body. html is included when you fetch a single message.
extracted_textOnly the new text. Quoted history, forwarded content and signatures are removed. Feed this to a model to save tokens.
extracted.otpThe most likely one-time code (482913, 731 902 → 731902, K7Q2MZ). Order numbers, phone numbers, years and promo codes are ignored.
extracted.action_linkThe most likely verify, sign-in, reset or confirm link. links lists all links, with unsubscribe and tracking links removed.
authSPF, DKIM and DMARC results from our receiving server: pass, fail, softfail, neutral, none, …
safety.prompt_injectionnone, suspected or likely, with reasons such as instruction_override or hidden_text. See Prompt-injection safety.
statusInbound: received. Outbound: queued → sent → delivered, or bounced, complained, failed.
expiredtrue once the inbox's retention period has passed: the body, extracted fields and attachments are gone.

Listing and searching

const page = await send0.messages.list(inbox.id, {
  from: "*@acme.com",       // exact address or wildcard
  subject: "invoice",        // case-insensitive substring
  q: '"purchase order"',     // full-text search over subject and body
  direction: "in",
  since: "2026-10-01T00:00:00Z",
  limit: 20,
});

for await (const m of page) { /* walks every page */ }

Attachments and the original message

const att = await send0.messages.attachment(msg.id, msg.attachments[0].id);
await fetch(att.download_url); // valid until att.expires_at (15 minutes)

const rawUrl = await send0.messages.rawUrl(msg.id); // the original .eml, same 15-minute link

Threads

A thread is a conversation inside one inbox. send0 groups messages like a mail client does:

  1. In-Reply-To: the message it answers.
  2. References: any earlier message in the chain.
  3. Fallback: the same subject (ignoring Re:, Fwd:, AW:, SV: and similar) with at least one shared participant, within 14 days.

Every message send0 sends has Message-ID: <msg_…@send0.email>, so replies to it map straight back to the right thread.

for await (const t of await send0.threads.list(inbox.id)) console.log(t.subject, t.message_count);

const thread = await send0.threads.get(inbox.id, "thr_3Lp8…"); // messages oldest first
const withHtml = await send0.threads.get(inbox.id, "thr_3Lp8…", { include_html: true });

Each thread in a list carries latest_message: who sent the newest message, which way it went, and a short preview, so you can show an inbox without fetching every thread:

{
  "object": "thread",
  "id": "thr_3Lp8…",
  "subject": "Re: PO #4471 delivery date",
  "message_count": 2,
  "latest_message": {
    "id": "msg_9Kd2…",
    "direction": "in",
    "from": { "name": "Dana Rivera", "email": "dana@acme.com" },
    "snippet": "Can you confirm Thursday instead? Our dock is closed Wednesday."
  },
  "last_message_at": "2026-10-07T09:12:00Z"
}

The snippet is the start of the new text only (quoted history and signatures removed), with whitespace collapsed.

On this page