Skip to content
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 recipients/hour by default, where a message to three people counts as three, and at most 50 recipients (To and Cc) on one message. Going over the hourly limit 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, 403 if 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.