Developers

API

A small REST API for the things you'd automate: list your domains, manage addresses, and send mail. Scoped bearer tokens, predictable errors, documented limits.

Base URL & authentication

Every endpoint lives under https://app.mailhow.com/api/v1 and authenticates with a bearer token. Create one in Settings → API tokens, choosing only the scopes it needs. The token is shown once — store it somewhere safe.

curl https://app.mailhow.com/api/v1/domains \
  -H "Authorization: Bearer mh_your_token_here"

A missing or invalid token returns 401. A token without the scope an endpoint requires returns 403.

Scopes

ScopeGrants
domains:readList your connected domains.
addresses:readList addresses and aliases.
addresses:writeCreate and delete aliases.
messages:sendSend mail from your addresses.

Rate limits

60 requests per minute, per token. Exceeding it returns 429 with a Retry-After: 60 header.

Sending is additionally capped per account — the same hourly limit that applies in the web app (100 messages/hour by default). Hitting it returns 429. This protects the shared sending reputation that keeps your mail landing in inboxes; see limits & fair use.

Errors

Errors are always JSON with a single human-readable field:

{ "error": "Token is missing the \"addresses:write\" scope." }

400 bad request · 401 bad token · 403 missing scope or not yours · 404 not found · 409 conflict · 429 rate limited.

Endpoints

GET/api/v1/domainsscope: domains:read

Your connected domains and their verification state.

{
  "domains": [
    {
      "id": "…",
      "domain": "example.com",
      "state": "verified",
      "verifiedAt": "2026-07-01T10:00:00.000Z",
      "strictDmarc": true,
      "createdAt": "2026-06-20T09:00:00.000Z"
    }
  ]
}
GET/api/v1/addressesscope: addresses:read

Every address on the account: the primary one, plus each alias. masked aliases carry a lifecycle status; managed addresses are operator-controlled and read-only.

{
  "addresses": [
    { "email": "you@example.com", "primary": true,  "managed": false, "masked": false, "label": null, "status": null },
    { "email": "hello@example.com", "primary": false, "managed": false, "masked": false, "label": null, "status": null }
  ]
}
POST/api/v1/addressesscope: addresses:write

Add an alias on one of your connected domains. Returns 409 if it already exists, 403if the domain isn’t yours.

curl -X POST https://app.mailhow.com/api/v1/addresses \
  -H "Authorization: Bearer mh_…" \
  -H "Content-Type: application/json" \
  -d '{"address":"hello@example.com"}'

→ 201  { "address": "hello@example.com" }
DELETE/api/v1/addresses/{address}scope: addresses:write

Remove an alias. URL-encode the address. The primary address, masked aliases and operator-managed addresses are refused with 409.

curl -X DELETE https://app.mailhow.com/api/v1/addresses/hello%40example.com \
  -H "Authorization: Bearer mh_…"

→ 200  { "ok": true }
POST/api/v1/messagesscope: messages:send

Send a message. from must be an address you own. Sending requires a paid plan (402 otherwise) and is subject to the hourly cap above.

curl -X POST https://app.mailhow.com/api/v1/messages \
  -H "Authorization: Bearer mh_…" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "you@example.com",
    "to": ["someone@elsewhere.com"],
    "subject": "Hello",
    "text": "Sent via the MailHow API."
  }'

→ 201  { "id": "…" }

Optional fields: cc (array), html (string).

Versioning

This is v1. Breaking changes ship under a new version prefix — we won’t change v1 responses out from under you. Webhooks are on the roadmap.