Skip to main content

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.

  1. 1Create an account and your own workspace (free).
  2. 2Open Settings → API keys, create a key and choose its scopes.
  3. 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 workspace
Base URL
https://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
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.

ScopeAllows
readRead mailboxes, mail, OTP codes and Inbox/Spam placement
moveMove mail out of Spam and between folders
importAdd mailboxes
syncRequest an immediate sync
sendSend, reply to and forward mail
managePause, resume and delete mailboxes
webhooksManage webhooks

Errors

Errors always carry a stable code and an English message. Handle errors by code, never by message text.

HTTPCodeMeaning
401unauthorizedAPI key missing, invalid, expired or revoked
403forbiddenThe key lacks the required scope or the IP is not on its allowlist
404not_foundThe mailbox or message does not exist or does not belong to the key's workspace
409mailbox_unavailableThe mailbox needs reconnecting, is paused, or is IMAP-only (not supported yet)
422validation_errorThe request data is malformed
429rate_limitedMore than 300 requests per minute for this key — wait for the Retry-After header
503upstream_errorMicrosoft is failing or throttling — try again later
500internal_errorSystem 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
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/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
Import mailboxes and check progress
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}/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
Send mail
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}/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
Inbox or Spam? Then move it out of Spam
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.

EventWhen
message.receivedNew mail arrived in Inbox
message.junkNew mail landed in Spam
mailbox.disconnectedThe 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/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
Verify a webhook signature (Node.js)
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.

Import mailboxes and check progress
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
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)
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
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
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)
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
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"])
Verify a webhook signature (Node.js)
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));