Skip to main content

Quickstart — send your first email in one call

Key → first send (your agent is created automatically) → first reply event. The five-minute path through the REST API, with copy-paste curl and fetch.
Use this pre-built prompt to get started faster.
Open in Cursor

This is the five-minute path from nothing to a delivered email and a structured reply event. Every call here is plain HTTPS against https://api.mails.ai with an Authorization: Bearer header — no SDK to install, no glue code, and no setup step before the first send. If you prefer a wrapper, the TypeScript SDK and MCP server are live on npm today and wrap exactly this REST surface; the API below is the interface today and the foundation underneath every SDK.

  1. 1. Get an API key

    Sign in at app.mails.ai (magic link) and mint a key under API keys. Minting is self-serve — no request, no waiting. Keys look like mk_live_… (or mk_test_… for the test mode) and are shown exactly once at creation — store it in an environment variable:

    Terminal
    export MAILS_API_KEY="mk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

    Every request authenticates with that key in the standard bearer header. A request with no key gets a structured 401 telling you exactly what is missing — see Authentication for scopes, key modes, and rotation.

  2. 2. Send your first email

    POST /v1/messages sends one message, and it is the whole setup: it sends as hello, the agent every new workspace gets at signup, and if your workspace has no agent yet, this call creates it as it sends. Give it a from, a recipient, a subject, and a text or HTML body — the same request you would write for any email API. The recipient below, reply@test.mails.ai, is an address of ours that answers within about a second — so by the time you reach step 4 the reply is already in your agent’s inbox. Swap in any real address once you have seen it work. How the test address works.

    cURL
    curl https://api.mails.ai/v1/messages \
      -H "Authorization: Bearer $MAILS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "from": "hello@yourapp.com",
        "to": "reply@test.mails.ai",
        "subject": "Welcome aboard",
        "body_text": "Thanks for signing up — reply any time and I will read it."
      }'

    The same call in TypeScript, using nothing but fetch:

    TypeScript
    const res = await fetch("https://api.mails.ai/v1/messages", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.MAILS_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        from: "hello@yourapp.com",
        to: "reply@test.mails.ai",
        subject: "Welcome aboard",
        body_text: "Thanks for signing up — reply any time and I will read it.",
      }),
    });
    
    const message = await res.json();
    console.log(message.id, message.status); // msg_01J9…  delivered

    A 201 returns the sent message, and cost_usd is an estimate of the send’s delivery cost, not what your plan charges:

    JSON
    {
      "id": "msg_01J9X2K7Q8ABCDEF...",
      "agent_id": "agt_01J9X2K7Q8WERTY...",
      "thread_id": "thrd_01J9X2K7Q8...",
      "to": ["reply@test.mails.ai"],
      "subject": "Welcome aboard",
      "classifier_score": 0.03,
      "status": "delivered",
      "cost_usd": 0.0001,
      "created_at": "2026-08-05T17:25:11.004Z"
    }

    Two things to know about that call. from is a naming hint, not a spoofable identity: its local part names the agent that sends it (here hello), and the mail actually sends from hello.<your-workspace>@send.mails.ai until you verify a custom domain. And if the workspace already has an agent with that name, the call simply uses it; with several, pass "agent" to say which.

    Got 422 cold_email_prohibited? mails.ai carries transactional and conversational mail only — cold outreach is refused at the API. Iterate without transmitting anything using an mk_test_ key, and if you believe a refusal is wrong, POST /v1/messages/appeal gets you an independent second review.
  3. 3. Meet your agent

    The send you just made was by an agent — the thing mails.ai is actually about. An agent is a named sending identity that owns an address, a reputation score, optional send limits — and an inbox. Mail sent to its inbound address <name>.<workspace>@in.mails.ai (here hello.acme@in.mails.ai) lands with your agent, parsed and scored; outbound mail carries that address as its Reply-To, so replies route back automatically. That receive-and-reply half is what a plain send API doesn’t do.

    You can also create agents explicitly — to pick names deliberately, run several identities, or set per-agent limits. Free holds one agent, so a second one needs Pro, which holds five. One field; the name goes into the agent's addresses, so sarah receives mail at sarah.<workspace>@in.mails.ai:

    cURL
    curl https://api.mails.ai/v1/agents \
      -H "Authorization: Bearer $MAILS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "name": "sarah" }'

    Find the auto-created one any time in the dashboard under Agents.

  4. 4. Receive the reply

    reply@test.mails.ai has already answered the message from step 2 (any real recipient works the same way, just not in one second). The inbound is parsed into a structured event — with injection_score and sender_reputation on every reply within your plan’s limits, plus intent and entities read from first-contact mail if you enable classification on the agent. There are two ways to consume it.

    Poll the events feed — simplest to start with:

    cURL
    # A genuine reply to one of your sends emits reply.received.
    # (message.received is a cold/first-contact inbound, not a reply.)
    curl "https://api.mails.ai/v1/events?event_type=reply.received" \
      -H "Authorization: Bearer $MAILS_API_KEY"

    Or register a webhook so events are pushed to your endpoint as they arrive (HMAC-signed, retried with backoff):

    cURL
    curl https://api.mails.ai/v1/webhooks \
      -H "Authorization: Bearer $MAILS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "url": "https://your-app.com/inbound",
        "event_types": ["reply.received", "message.received"]
      }'

    For the live tail — a long-lived stream of every event as it happens — see the events & streaming guide. For verifying webhook signatures, see webhooks.

What you just built

A working agent address that sends real email and turns every reply into a structured, scored event your code can branch on — from one API call, with no setup step. From here:

  • Authentication — key scopes (send / read / manage), test vs live mode, rotation.
  • Concepts — how workspaces, agents, messages, events, and threads relate.
  • API reference — every endpoint, request and response shape, with the exact fields.
  • Errors — the error envelope and every code you can get back.

Common questions

Do I need to install an SDK?

No. The mails.ai API is plain HTTPS + JSON — everything in this quickstart is curl and fetch. If you'd rather use a wrapper, the TypeScript SDK (@mailsai/sdk), the Python SDK (mailsai), and the MCP server (@mailsai/mcp-server) are live today. All are thin wrappers over this same REST surface, which is — and always will be — the canonical interface.

Why was my send refused with 422 cold_email_prohibited?

mails.ai carries transactional and conversational mail only — cold outreach is refused at the API, not in the fine print. Messages shaped like unsolicited pitches get a 422 naming the verdict and what a passing message looks like. To iterate without transmitting anything, send with an mk_test_ key (or the composer's Send test). If you believe a refusal is wrong, POST /v1/messages/appeal — an independent reviewer re-judges the exact content under the same policy, and an overturned verdict clears it to send for 24 hours.

Where does the email actually send from?

From the shared sending domain, as <agent>.<workspace-slug>@send.mails.ai under the agent's name — e.g. hello.acme@send.mails.ai for the agent whose address in the API is hello@acme.mails.ai. Each agent is a named sending identity inside your workspace with its own address, reputation, and optional per-agent send limits. If you never created one, signup did, and named it hello. The API address is not an inbox: to email an agent, write to <agent>.<workspace-slug>@in.mails.ai (e.g. hello.acme@in.mails.ai) — the workspace subdomain itself does not receive mail. Custom sending domains are live on paid plans: verify DKIM once and your agents send as you@yourdomain.com.

Was this page helpful?