email59

Docs

email59 is an email router. You call one JSON API and we route the message to the sending provider. Base URL: https://email59.com/v1.

Authenticate with Authorization: Bearer <key>, where the key is an API key (e59_live_...) or a session key from email sign-in (e59s_...). Agents can use the MCP server instead of the REST calls. One email = one recipient. Every account gets 200 emails a day free, renewed every day. Each $1 buys 1,000 email credits that last until used. Mail is sent from <name>@email59.com: there is no domain to set up. No subscriptions.

Interactive reference (OpenAPI): /v1/docs.

Sign up with an email code

Bots, scripts and agents sign up or sign in with two calls to POST /v1/auth. The first emails a 6-digit code to the address. The second sends the code back and returns a session key. A new address is signed up on the spot with 200 emails a day free; an existing one is signed in.

# 1. Ask for a code
curl -s https://email59.com/v1/auth -H 'Content-Type: application/json'   -d '{"email":"you@yourapp.com"}'
# -> {"otp_sent": true, "expires_in_seconds": 600, "next": "Call auth again with the same email and the 6-digit otp..."}

# 2. Send the code back
curl -s https://email59.com/v1/auth -H 'Content-Type: application/json'   -d '{"email":"you@yourapp.com","otp":"123456"}'
# -> {"session_key": "e59s_...", "token_type": "Bearer", "expires_at": "...", "new_account": true, "account": {...}}

Send the session key as Authorization: Bearer e59s_... on every call that takes an API key. It lasts 30 days. Then:

POST   /v1/keys     {"label":"server"}             -> {"api_key": "e59_live_..."} a long-lived key, shown once (10 max)
GET    /v1/keys                                    -> your keys (labels and dates, never the key itself)
DELETE /v1/keys/{id}                               revoke a key
POST   /v1/auth     {"api_key":"e59_live_..."}       swap an API key for a session key
POST   /v1/auth/logout                             end the session key in the Authorization header
RuleValue
Code lifetime10 minutes, single use. 5 wrong tries end the code.
Codes per address3 per 15 minutes.
New addressesMust be a real mailbox: disposable addresses and domains without MX are refused.
AccountsOne per email, shared with the account page sign-in and /v1/signup.

Sign up with a key

POST /v1/signup with your email. Returns an API key (shown once). Every account gets 200 emails a day free. People can sign in on the account page with osec.one instead and create keys there.

curl -s https://email59.com/v1/signup -H 'Content-Type: application/json' \
  -d '{"email":"you@yourapp.com","name":"Ada","company":"Yourapp"}'
FieldNotes
emailYour login address. One account per address.
name, companyOptional.

MCP server

email59 is also an MCP server for AI agents: https://email59.com/v1/mcp (JSON-RPC 2.0 over HTTP POST). Tools in OpenAI / Gemini function-calling format: /v1/mcp/schema.

Sign-in works like the REST API: call the auth tool with email, then again with email and otp. Send the returned session_key as Authorization: Bearer <session_key> on later requests. If your MCP client sets headers in its config, an API key works there too.

{
  "mcpServers": {
    "email59": {
      "type": "http",
      "url": "https://email59.com/v1/mcp",
      "headers": { "Authorization": "Bearer e59_live_..." }
    }
  }
}
curl -s https://email59.com/v1/mcp -H 'Content-Type: application/json'   -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"auth","arguments":{"email":"you@yourapp.com"}}}'
ToolDoes
authEmail code sign-up and sign-in, or api_key to session key. No key needed.
get_pricingFree emails, credits and packs. No key needed.
check_emailDeep check of one address with a score (same as GET /v1/check). No key needed.
get_account, update_accountToday's numbers and credits; set name and company.
send_emailOne transactional email (same as POST /v1/send).
send_campaignMarketing to up to 1,000 recipients (same as POST /v1/campaigns).
validate_emailVerdict for one address.
block, unblock, list_blocklist, add_unsubscribeRecipient lists.
buy_pack, order_statusA Checkout link for a person to pay, and its status.
create_api_key, list_api_keysLong-lived keys for servers.

Tool errors come back as isError: true with the same error and status as the REST API (see Errors).

Send

POST /v1/send is for transactional mail: one recipient per call (receipts, codes, alerts). Newsletters and promotions go through campaigns.

curl -s https://email59.com/v1/send \
  -H 'Authorization: Bearer e59_live_...' -H 'Content-Type: application/json' \
  -d '{"from":"hello","to":"user@gmail.com","subject":"Your report",
       "html":"<p>Ready.</p>","text":"Ready."}'
FieldNotes
fromOptional. The name before the @; the domain is always email59.com. Any other domain is refused (from_must_use_email59_com).
toOne address.
subject, html, textAt least one of html or text.

Every email says it is a system generated email and that replies are not received. Unless your content already has an unsubscribe link, email59 adds one: it names the person and the sender, and one press stops your mail to that address (see Unsubscribe). Transactional mail still goes to people who unsubscribed; only campaigns skip them. Blocked and invalid addresses are refused.

Campaigns (marketing)

POST /v1/campaigns sends one marketing email to up to 1,000 recipients through Zoho Campaigns. Zoho adds the unsubscribe link and processes opt-outs. One email of today's allowance per accepted recipient.

curl -s https://email59.com/v1/campaigns \
  -H 'Authorization: Bearer e59_live_...' -H 'Content-Type: application/json' \
  -d '{"from":"news","subject":"New in May","html":"<p>Hello</p>",
       "list_id":"news","recipients":["user@gmail.com","ada@example.org"]}'
FieldNotes
fromOptional, as for /v1/send: the name before @email59.com.
recipients1 to 1,000 addresses. Duplicates are merged.
list_idYour name for the audience. Opt-outs for this list (or for all lists) are skipped.

The response lists accepted and rejected (with a verdict for each address you did not send to). Invalid, disposable, role (info@, support@), blocked and unsubscribed addresses cost nothing.

Validate

POST /v1/validate checks an address without sending. Returns verdict: ok, invalid, no_mx, disposable or role.

curl -s https://email59.com/v1/validate -H 'Authorization: Bearer e59_live_...' \
  -H 'Content-Type: application/json' -d '{"email":"info@acme.com"}'
# {"verdict":"role", ...}

Deep check (free, no key)

GET /v1/check?email=... (or POST /v1/check with {"email":"..."}) scans one address: format, the provider's username rules, disposable and role addresses, and the domain's MX, SPF, DMARC, DKIM (common selectors), MTA-STS, TLS-RPT and BIMI records. Gmail, Outlook, Yahoo, iCloud and other big providers skip DNS. No email is sent and no mailbox (SMTP) test is made, so the score (0 to 99) is an estimate. Try it on the email check page.

Free: 100 checks a day per IP without a key, 1,000 a day per account with Authorization: Bearer (an API or session key). Over the limit: 429 daily_check_limit.

curl -s 'https://email59.com/v1/check?email=sam@acme.com'
# {"email":"sam@acme.com","domain":"acme.com","provider":"Google Workspace","popular_provider":false,
#  "score":95,"verdict":"likely_valid","summary":"The domain is set up to receive email; ...",
#  "checks":[{"name":"format","status":"pass",...},{"name":"mx","status":"pass","records":["1 smtp.google.com"]},
#            {"name":"spf",...},{"name":"dmarc",...,"policy":"reject"},{"name":"dkim",...}, ...],
#  "note":"No email was sent and no mailbox (SMTP) test was made. ...",
#  "quota":{"limit_per_day":100,"used_today":1,"resets_at":"..."}}

Verdicts: likely_valid (80+), risky (40 to 79, also throwaway services), likely_invalid (under 40: bad format, no domain, no or null MX) and unknown (DNS timed out). Each check has a status: pass, warn, fail, info or skip.

Blocklist

Your own list of addresses and domains. Blocked recipients are refused before they count against your day.

POST   /v1/blocklist   {"kind":"email","value":"bad@example.org","reason":"bounced"}
POST   /v1/blocklist   {"kind":"domain","value":"spam.example"}
GET    /v1/blocklist
DELETE /v1/blocklist?kind=email&value=bad@example.org

Unsubscribe lists

Transactional email carries its own unsubscribe link. It opens a page at /v1/unsubscribe/{token} that names the person (for example user@gmail.com) and the sender, and asks for one press. Pressing it stops campaigns from that sender to that address. Opening the link alone changes nothing, so link scanners can't unsubscribe anyone.

Campaign mail carries Zoho Campaigns' unsubscribe link. Record opt-outs here so later campaigns skip them, for example when a person asks you directly or when you sync opt-outs from Zoho.

POST /v1/unsubscribes   {"email":"user@gmail.com","list_id":"news"}   // empty list_id = all lists
GET  /v1/unsubscribes?email=user@gmail.com

Allowance

200 emails a day free, renewed every day (00:00 UTC). Unused free emails don't carry over. Each $1 buys 1,000 email credits. Credits don't renew and don't expire: they last until used. Each day, free emails are used first, then credits. Check the numbers with GET /v1/account (daily_limit, sent_today, remaining_today, credits_remaining). Packs and prices: /v1/pricing.

Account console

People sign in on the account page with osec.one (email code, Google or Apple). The console sends the osec session as Authorization: Bearer. These calls use the same session:

GET    /v1/web/account                 -> daily_limit, sent_today, credits_remaining, name, company
PATCH  /v1/web/account  {name, company}
GET    /v1/web/keys                    -> keys (labels and dates, never the key itself)
POST   /v1/web/keys     {label}        -> {id, api_key}   (api_key shown once; up to 10 keys)
DELETE /v1/web/keys/{id}               -> revokes the key at once

Bots don't need the console: email sign-in (or POST /v1/signup) and the bearer key do everything above with /v1/account and /v1/keys.

Buy a pack

Packs are one-time payments through Stripe Checkout. The amount is added to the account when the payment clears, once per order, even if Stripe sends the notification more than once.

GET  /v1/policy                              -> {"version": "...", "refund": "...", "terms": [...]}
POST /v1/web/orders  {"pack_id":"pack_10","agree":true,"policy_version":"..."}   (console session)
POST /v1/orders     {"pack_id":"pack_10","agree":true,"policy_version":"..."}   (API key, for bots)
  -> {"order_id": 12, "status": "pending", "checkout_url": "https://checkout.stripe.com/..."}
GET  /v1/orders/12                           (API key) -> {"status": "paid", "emails": 10000}

Pack ids: pack_1 ($1, +1,000 email credits), pack_10 ($10, +10,000), pack_50 ($50, +50,000). Credits don't renew and last until used. Before any order, show the person the refund policy and terms from GET /v1/policy and send agree: true with its version only once they agree; without it the order is refused with terms_not_agreed. Payments are one-time, there is no subscription or automatic charge, and they are not refundable in general. Send a person to checkout_url to pay. Orders list at GET /v1/web/orders in the console.

Errors

StatusDetailMeaning
400from_must_use_email59_com, invalid_from, empty_body, invalid_emailFix the request. The from domain is always email59.com.
401missing_api_key, invalid_api_keySend the bearer key.
401sign_in_required, session_expiredConsole calls need a current osec.one session; an expired e59s_ key needs a new POST /v1/auth.
400wrong_code, no_valid_code, email_rejectedEmail sign-in: check the code, ask for a new one, or use a real mailbox.
429too_many_codes3 codes per address per 15 minutes.
409too_many_keysRevoke an unused key first (10 per account).
429daily_limit_reachedToday's free emails are used up and there are not enough credits. Buy credits, or wait for resets_at (00:00 UTC) for the free emails. Nothing was sent.
429daily_limit_reachedCampaigns need one email (free or credit) per accepted recipient. A campaign that doesn't fit is refused whole.
422recipient_rejected with a verdictinvalid, no_mx, disposable, blocked on /v1/send. Nothing counted against your day.
422use_campaignsMarketing mail was sent to /v1/send. Use /v1/campaigns.
422no_deliverable_recipientsEvery campaign recipient was rejected (see rejected). Nothing counted against your day.
429signup_limit_reachedToo many signups from this IP today.
502provider_errorThe provider failed. The email was given back (free emails or credits).
422unknown_packUse pack_1, pack_10 or pack_50.
502payment_provider_errorStripe did not answer. Try again.
503sending_not_configured, campaigns_not_configured, payments_not_configuredThat path is off on this server.