Quickstart — send your first email in one call
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. 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 likemk_live_…(ormk_test_…for the test mode) and are shown exactly once at creation — store it in an environment variable: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. Send your first email
POST /v1/messagessends one message, and it is the whole setup: it sends ashello, 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 afrom, 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 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: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… deliveredA
201returns the sent message, andcost_usdis an estimate of the send’s delivery cost, not what your plan charges:{ "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.
fromis a naming hint, not a spoofable identity: its local part names the agent that sends it (herehello), and the mail actually sends fromhello.<your-workspace>@send.mails.aiuntil 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.Got422 cold_email_prohibited? mails.ai carries transactional and conversational mail only — cold outreach is refused at the API. Iterate without transmitting anything using anmk_test_key, and if you believe a refusal is wrong,POST /v1/messages/appealgets you an independent second review.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(herehello.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
sarahreceives mail atsarah.<workspace>@in.mails.ai: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. Receive the reply
reply@test.mails.aihas 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 — withinjection_scoreandsender_reputationon every reply within your plan’s limits, plusintentandentitiesread 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:
# 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 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
Was this page helpful?