Integrations
Using the API
Read conversations, set up domains, and send mail over HTTP, with the endpoints, examples, and errors you will meet.
The MailMoose API lets you read conversations and send mail from your own scripts and tools. Every request carries an API key and acts as the person who created it.
The base path is /api/v1. All requests and responses are JSON.
Authenticate
Send your key as a bearer token:
Authorization: Bearer mm_live_your_key_here
Check that a key works before doing anything else:
curl https://your-mailmoose-url/api/v1/me \
-H "Authorization: Bearer mm_live_your_key_here"
The response tells you which permissions the key has and which addresses it can send from.
Read conversations
List the most recent conversations:
GET /api/v1/threads
Accepts limit, unread=true, address to restrict to one mailbox, and q to search subjects, senders, and message text.
Only conversations you have received mail in are listed. Mail you sent that nobody has answered is not a conversation yet, and an app that sends a lot of it would bury everything that is. Pass include=all to list those too.
Read a single conversation with all its messages:
GET /api/v1/threads/{id}
Each message includes its authentication result. A message that failed DKIM or DMARC is far more likely to be forged, so check it before acting on anything financial.
Reply to a conversation
POST /api/v1/threads/{id}/reply
{ "body": "Thursday works for us." }
Notice there is no recipient. MailMoose addresses the reply using the conversation's own records, so a reply cannot be sent somewhere the conversation did not come from. Pass "replyAll": true to include everyone on the original message.
Send a new message
POST /api/v1/messages
{
"from": "thomas@yourdomain.com",
"to": ["jane@example.com"],
"subject": "Quote for January",
"body": "Hi Jane, here is the quote we discussed."
}
If you have never corresponded with a recipient, this returns 409 with the code confirmation_required and names the unfamiliar addresses. Repeat the request with "confirmNewRecipient": true to send anyway.
That step exists so an automated caller cannot quietly start mailing strangers.
Set up a domain
Connect a domain you own. The response carries the DNS records still to add and, for each one, whether to add it, edit what is already there, or replace it:
POST /api/v1/domains
{ "name": "yourdomain.com", "label": "Your business" }
A domain this organization already has answers 200 with "created": false and its current setup, rather than an error, so a retry after a dropped response is harmless.
Check it again once the records are in place:
GET /api/v1/domains/{name}
This asks public DNS and the mail provider on every call, so it takes a few seconds. It is for after a change, not for polling.
Receiving and sending verify separately. Connecting a domain sets up receiving; turn on sending for it with:
POST /api/v1/domains/{name}/sending
That answers with the same setup shape, now including the records that authorise sending. Safe to repeat: it re-syncs rather than registering the domain twice, and asks the sending provider to check again.
All three need the Manage domains permission, and connecting is limited to owners and admins.
Manage addresses
GET /api/v1/addresses
POST /api/v1/addresses
{ "localPart": "hello", "domain": "yourdomain.com" }
Creating an address needs the Manage domains permission, and the domain needs at least one DNS record verified. GET /api/v1/domains/{name} says where it stands.
Sending limits
Defaults are 100 sends per key and 1,000 per organization, counted over the previous 24 hours. Key sends count toward both limits. How limits work.
Errors
Every error has the same shape:
{ "error": { "code": "insufficient_scope", "message": "..." } }
| Status | Code | What to do |
|---|---|---|
| 401 | unauthorized | The key is wrong, revoked, or expired. Create a new one. |
| 403 | insufficient_scope | The key lacks the permission this endpoint needs. |
| 404 | not_found | No such conversation, or it belongs to somebody else. |
| 409 | confirmation_required | Resend with confirmNewRecipient set to true. |
| 429 | rate_limited | The key or organization send limit is reached. Wait for older sends to leave the 24-hour window. |
| 400 | bad_request | Something in the request body is wrong. The message says what. |
A conversation belonging to another account returns 404 rather than 403, so identifiers cannot be used to discover what exists.
Related
Last updated 2026-09-15