Documentation
Mailera API
Manage Microsoft mailboxes (Outlook, Hotmail, Microsoft 365) end to end over REST: add mailboxes, check they are alive, read mail, fetch OTP codes, send mail, move mail out of Spam and receive webhooks.
Introduction
Every endpoint lives under one base URL and accepts and returns JSON.
- 1Create an account and your own workspace (free).
- 2Open Settings → API keys, create a key and choose its scopes.
- 3Send the key in the X-API-Key header on every request.
Successful responses carry a data field; errors carry an error field with code and message.
Create a free workspacehttps://mailera.app/api/public/v1# 200{ "data": [ ... ], "next_cursor": "..." }# 4xx / 5xx{ "error": { "code": "not_found", "message": "..." } }Authentication
Send your API key in the X-API-Key header. Keys start with mh_live_ and are shown only once when created; if you lose one, create a new key and revoke the old one.
An Authorization header with a Bearer token holding the same key is also accepted.
Keys can be restricted to an IP allowlist; calls from other IPs get a 403.
curl https://mailera.app/api/public/v1/mailboxes \ -H "X-API-Key: mh_live_..."Key scopes
Each key carries one or more scopes. Calling an endpoint the key has no scope for returns 403 forbidden.
| Scope | Allows |
|---|---|
read | Read mailboxes, mail, OTP codes and Inbox/Spam placement |
move | Move mail out of Spam and between folders |
import | Add mailboxes |
sync | Request an immediate sync |
send | Send, reply to and forward mail |
manage | Pause, resume and delete mailboxes |
webhooks | Manage webhooks |
Errors
Errors always carry a stable code and an English message. Handle errors by code, never by message text.
| HTTP | Code | Meaning |
|---|---|---|
| 401 | unauthorized | API key missing, invalid, expired or revoked |
| 403 | forbidden | The key lacks the required scope or the IP is not on its allowlist |
| 404 | not_found | The mailbox or message does not exist or does not belong to the key's workspace |
| 409 | mailbox_unavailable | The mailbox needs reconnecting, is paused, or is IMAP-only (not supported yet) |
| 422 | validation_error | The request data is malformed |
| 429 | rate_limited | More than 300 requests per minute for this key — wait for the Retry-After header |
| 503 | upstream_error | Microsoft is failing or throttling — try again later |
| 500 | internal_error | System error |
Limits and pagination
Each key gets 300 requests per minute. Going over returns 429 with a Retry-After header.
Lists return next_cursor; send it back as the cursor parameter to get the next page. When there is no more data, next_cursor is null.
The limit parameter sets the page size.
curl "https://mailera.app/api/public/v1/mailboxes?limit=50&cursor=NEXT_CURSOR" \ -H "X-API-Key: mh_live_..."Mailboxes
Add, check, sync, pause and delete mailboxes.
- GET
/mailboxesList mailboxes
- Scope:
read- Query:
- status, q, cursor, limit
- POST
/mailboxes/importBulk import mailboxes (email:password:refresh_token:client_id, or | separated)
- Scope:
import- Body:
- lines, text, group_id, duplicate_mode
- GET
/mailboxes/importImport progress and per-line results
- Scope:
import- Query:
- job_id
- GET
/mailboxes/{id}Mailbox details and folder counters
- Scope:
read
- PATCH
/mailboxes/{id}Pause or resume a mailbox
- Scope:
manage- Body:
- action (required)
- DELETE
/mailboxes/{id}Delete a mailbox
- Scope:
manage
- GET
/mailboxes/{id}/statusCheck live with Microsoft whether the mailbox still works
- Scope:
read
- POST
/mailboxes/{id}/syncSync now
- Scope:
sync- Body:
- wait
curl -X POST https://mailera.app/api/public/v1/mailboxes/import \ -H "X-API-Key: mh_live_xxx" -H "content-type: application/json" \ -d '{"lines":["user@hotmail.com:pass:M.C5_refresh...:9e5f94bc-..."]}'# → { "data": { "job_id": "..." } }curl "https://mailera.app/api/public/v1/mailboxes/import?job_id=JOB_ID" -H "X-API-Key: mh_live_xxx"Messages
Read, search, fetch OTP codes and send mail.
- GET
/mailboxes/{id}/messagesList messages in a folder
- Scope:
read- Query:
- folder, unread, cursor, limit
- GET
/mailboxes/{id}/messages/{msgId}Get one message with its content
- Scope:
read
- GET
/mailboxes/{id}/otpGet the latest OTP code
- Scope:
read- Query:
- from, subject, since_minutes
- POST
/mailboxes/{id}/sendSend, reply or forward
- Scope:
send- Body:
- to, cc, bcc, subject, html, text, importance, reply_to_message_id, mode, attachments
- GET
/messages/searchSearch mail across all mailboxes
- Scope:
read- Query:
- q, folder, limit
curl -X POST https://mailera.app/api/public/v1/mailboxes/MAILBOX_ID/send \ -H "X-API-Key: mh_live_xxx" -H "content-type: application/json" \ -d '{"to":["customer@example.com"],"subject":"Hello","text":"Message body"}'Spam
Check Inbox/Spam placement and move mail out of Spam.
- POST
/mailboxes/{id}/messages/{msgId}/moveMove a message (for example from Spam to Inbox)
- Scope:
move- Body:
- destination (required)
- POST
/mailboxes/{id}/unspamMove all mail, or one sender's mail, out of Spam
- Scope:
move- Body:
- filter
- GET
/mailboxes/{id}/placementDid the mail land in Inbox or Spam
- Scope:
read- Query:
- from, subject, since_minutes
curl "https://mailera.app/api/public/v1/mailboxes/MAILBOX_ID/placement?from=me@example.com" -H "X-API-Key: mh_live_xxx"curl -X POST https://mailera.app/api/public/v1/mailboxes/MAILBOX_ID/unspam -H "X-API-Key: mh_live_xxx" \ -H "content-type: application/json" -d '{"filter":{"type":"sender","value":"me@example.com"}}'Webhooks
Register URLs that receive events.
For every event, Mailera sends a JSON POST to your URL. If your endpoint errors or does not answer, Mailera retries with increasing delays.
| Event | When |
|---|---|
message.received | New mail arrived in Inbox |
message.junk | New mail landed in Spam |
mailbox.disconnected | The mailbox lost its connection (token revoked, password changed…) |
To verify, compute HMAC-SHA256 of the timestamp, a dot and the body with the webhook secret, then compare it with the signature header.
Headers sent: X-MailHub-Event X-MailHub-Delivery X-MailHub-Timestamp X-MailHub-Signature
- GET
/webhooksList webhooks
- Scope:
webhooks
- POST
/webhooksCreate a webhook (the signing secret is returned once)
- Scope:
webhooks- Body:
- url (required), events, group_id
- PATCH
/webhooks/{id}Enable/disable a webhook or change its events or URL
- Scope:
webhooks
- DELETE
/webhooks/{id}Delete a webhook
- Scope:
webhooks
import crypto from "node:crypto";// X-MailHub-Signature: sha256=<hex> X-MailHub-Timestamp: <unix seconds>const expected = "sha256=" + crypto.createHmac("sha256", SECRET) .update(timestamp + "." + rawBody).digest("hex");const ok = expected.length === signature.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));Examples
Replace MAILBOX_ID and the key with your own values.
curl -X POST https://mailera.app/api/public/v1/mailboxes/import \ -H "X-API-Key: mh_live_xxx" -H "content-type: application/json" \ -d '{"lines":["user@hotmail.com:pass:M.C5_refresh...:9e5f94bc-..."]}'# → { "data": { "job_id": "..." } }curl "https://mailera.app/api/public/v1/mailboxes/import?job_id=JOB_ID" -H "X-API-Key: mh_live_xxx"curl https://mailera.app/api/public/v1/mailboxes/MAILBOX_ID/status -H "X-API-Key: mh_live_xxx"curl "https://mailera.app/api/public/v1/mailboxes/MAILBOX_ID/otp?from=noreply@service.com&since_minutes=15" \ -H "X-API-Key: mh_live_xxx"# → { "data": { "code": "482913", "folder": "inbox", ... } } or { "data": { "code": null } }curl -X POST https://mailera.app/api/public/v1/mailboxes/MAILBOX_ID/send \ -H "X-API-Key: mh_live_xxx" -H "content-type: application/json" \ -d '{"to":["customer@example.com"],"subject":"Hello","text":"Message body"}'curl "https://mailera.app/api/public/v1/mailboxes/MAILBOX_ID/placement?from=me@example.com" -H "X-API-Key: mh_live_xxx"curl -X POST https://mailera.app/api/public/v1/mailboxes/MAILBOX_ID/unspam -H "X-API-Key: mh_live_xxx" \ -H "content-type: application/json" -d '{"filter":{"type":"sender","value":"me@example.com"}}'const res = await fetch("https://mailera.app/api/public/v1/mailboxes?limit=50", { headers: { "X-API-Key": process.env.MAILERA_API_KEY },});const { data, next_cursor } = await res.json();import os, requestsr = requests.get( "https://mailera.app/api/public/v1/mailboxes/MAILBOX_ID/messages", params={"folder": "inbox", "limit": 20}, headers={"X-API-Key": os.environ["MAILERA_API_KEY"]},)print(r.json()["data"])import crypto from "node:crypto";// X-MailHub-Signature: sha256=<hex> X-MailHub-Timestamp: <unix seconds>const expected = "sha256=" + crypto.createHmac("sha256", SECRET) .update(timestamp + "." + rawBody).digest("hex");const ok = expected.length === signature.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
