# 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. - Base URL: https://mailera.app/api/public/v1 - OpenAPI 3.1: https://mailera.app/api/public/v1/openapi.json - HTML docs: https://mailera.app/en/docs - Authentication: `X-API-Key: mh_live_...` — Open Settings → API keys, create a key and choose its scopes. - 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. - Successful responses carry a data field; errors carry an error field with code and message. Success: `{ "data": ... }`. Error: `{ "error": { "code", "message" } }`. ## Key scopes - 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 ## Mailboxes Add, check, sync, pause and delete mailboxes. - GET /mailboxes — List mailboxes [scope: read] (query: status, q, cursor, limit) - POST /mailboxes/import — Bulk import mailboxes (email:password:refresh_token:client_id, or | separated) [scope: import] (body: lines, text, group_id, duplicate_mode) - GET /mailboxes/import — Import 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}/status — Check live with Microsoft whether the mailbox still works [scope: read] - POST /mailboxes/{id}/sync — Sync now [scope: sync] (body: wait) ## Messages Read, search, fetch OTP codes and send mail. - GET /mailboxes/{id}/messages — List 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}/otp — Get the latest OTP code [scope: read] (query: from, subject, since_minutes) - POST /mailboxes/{id}/send — Send, reply or forward [scope: send] (body: to, cc, bcc, subject, html, text, importance, reply_to_message_id, mode, attachments) - GET /messages/search — Search mail across all mailboxes [scope: read] (query: q, folder, limit) ## Spam Check Inbox/Spam placement and move mail out of Spam. - POST /mailboxes/{id}/messages/{msgId}/move — Move a message (for example from Spam to Inbox) [scope: move] (body: destination (required)) - POST /mailboxes/{id}/unspam — Move all mail, or one sender's mail, out of Spam [scope: move] (body: filter) - GET /mailboxes/{id}/placement — Did the mail land in Inbox or Spam [scope: read] (query: from, subject, since_minutes) ## Webhooks Register URLs that receive events. - GET /webhooks — List webhooks [scope: webhooks] - POST /webhooks — Create 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] ## Errors Errors always carry a stable code and an English message. Handle errors by code, never by message text. - 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 ## Webhooks For every event, Mailera sends a JSON POST to your URL. If your endpoint errors or does not answer, Mailera retries with increasing delays. - 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…) - Headers sent: X-MailHub-Event, X-MailHub-Delivery, X-MailHub-Timestamp, X-MailHub-Signature - To verify, compute HMAC-SHA256 of the timestamp, a dot and the body with the webhook secret, then compare it with the signature header. `X-MailHub-Signature: sha256=HMAC_SHA256(secret, timestamp + "." + body)` ## Examples ### Import mailboxes and check progress ```bash 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" ``` ### Check that a mailbox is alive ```bash curl https://mailera.app/api/public/v1/mailboxes/MAILBOX_ID/status -H "X-API-Key: mh_live_xxx" ``` ### Get the latest OTP code (last 15 minutes) ```bash 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 } } ``` ### Send mail ```bash 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"}' ``` ### Inbox or Spam? Then move it out of Spam ```bash 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"}}' ``` ### JavaScript (server side) ```js 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(); ``` ### Python ```python import os, requests r = 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"]) ``` ### Verify a webhook signature (Node.js) ```js import crypto from "node:crypto"; // X-MailHub-Signature: sha256= X-MailHub-Timestamp: 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)); ```