API

Get a test address, send a mail to it, read the report as JSON. Three requests, the same flow as the browser.

Authentication

Send the X-API-Key header on every request. You create a key on your account page and it is shown to you once.

X-API-Key: mt_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Without a key the request counts as anonymous and the per IP daily limit applies.

Step 1 — get a test address

curl -X POST https://mailtesterbox.com/generate \
  -H "X-API-Key: $MAILTESTER_KEY"
{
  "address": "test-a1b2c3d4e5f6a7b8c9d0@mailtesterbox.com",
  "expires_at": "2026-07-30T12:30:00+00:00",
  "expires_in": 1800,
  "limits": { "scope": "api_key", "limit": 25, "used": 3, "remaining": 22 }
}

The address lives 30 minutes and can be reused. Send a second mail to the same address and check again to get the newest report.

Step 2 — send a mail to that address

Send it from the server you want to test, exactly the way you normally send. We read the sending IP, the HELO name, the TLS version and every header from the connection itself.

Step 3 — ask for the report

curl "https://mailtesterbox.com/check/test-a1b2c3d4e5f6a7b8c9d0@mailtesterbox.com" \
  -H "X-API-Key: $MAILTESTER_KEY"

This endpoint both queues the analysis and returns its state. Poll it every few seconds until the mail arrives.

status Meaning
waitingThe address is live, no mail yet
processingThe mail arrived, analysis is running
analyzedThe report is ready, in the result field
limitYour daily allowance is used up
expiredThe address expired, get a new one
errorSomething went wrong during the analysis

If you send more than one mail to the same address, pass ?after=<event_id> so the mails you already saw are skipped and only the new one is awaited.

Endpoints

Endpoint Job
POST /generateNew test address
GET /check/{address}Queue the analysis and poll for it
GET /result/{address}Newest report, read only, never touches the quota
GET /limitsRemaining daily allowance
GET /historyYour past reports
GET /history/{id}One past report in full

For a schema level technical reference use the OpenAPI interface at /docs.

What the report contains

The result field carries the score out of 10, every deduction and all the checks.

{
  "status": "analyzed",
  "event_id": "6a6b12a4cc21db9abcd7ee74",
  "result": {
    "score": 9.2,
    "title": "Excellent",
    "issues": [
      { "code": "DMARC_POLICY_NONE", "points": -0.3, "severity": "low",
        "title": "DMARC policy is set to none", "how_to_fix": "..." }
    ],
    "checks": {
      "spf":    { "status": "ok", "result": "pass", "record": ["v=spf1 ip4:... -all"] },
      "dkim":   { "status": "ok", "verified": true, "selector": "default", "signing_domain": "..." },
      "dmarc":  { "status": "ok", "result": "pass", "policy": "none",
                  "spf_aligned": true, "dkim_aligned": true },
      "rdns":   { "success": true, "hostname": "mx.ornek.com", "matches": true },
      "helo":   { "value": "mx.ornek.com", "is_fqdn": true, "matches_rdns": true },
      "blacklists": { "checked": 20, "results": { "zen.spamhaus.org": "not_listed" } },
      "domain_blacklists": { "checked": 8, "listed": [] },
      "content": { "has_plain": true, "has_html": true, "text_ratio": 0.42,
                   "link_hosts": ["ornek.com"], "shortened_links": [] },
      "spamassassin": { "score": 0.1, "threshold": 5.0,
                        "rules": [ { "name": "TO_MALFORMED", "points": 0.1, "description": "..." } ] }
    },
    "connection": { "client_ip": "203.0.113.10", "helo": "mx.ornek.com",
                    "tls": true, "tls_protocol": "TLSv1.3" }
  }
}

The points values inside issues are negative and their sum is taken off 10. The code field is stable, so you can branch on it on your side.

Limits

Scope Analyses per day Request rate
Anonymous (IP)510 / minute
Account (browser)2510 / minute
API key25120 / minute

The daily allowance is charged when the analysis starts, not when the address is created, so creating an address, switching language or reading /result is free. Over the limit you get 429 with a Retry-After header.

Every one of these values is an environment variable if you run it on your own server. The project is open source: github.com/alialinx/mail-tester

Full example

#!/usr/bin/env bash
set -euo pipefail

KEY="$MAILTESTER_KEY"
BASE="https://mailtesterbox.com"

ADDRESS=$(curl -sS -X POST "$BASE/generate" -H "X-API-Key: $KEY" | jq -r .address)
echo "address: $ADDRESS"

swaks --to "$ADDRESS" --from "test@ornek.com" --server mail.ornek.com

for i in $(seq 1 60); do
  BODY=$(curl -sS "$BASE/check/$ADDRESS" -H "X-API-Key: $KEY")
  STATUS=$(echo "$BODY" | jq -r .status)
  [ "$STATUS" = "analyzed" ] && { echo "$BODY" | jq '.result.score, .result.issues'; break; }
  [ "$STATUS" = "expired" ] || [ "$STATUS" = "limit" ] && { echo "$BODY"; exit 1; }
  sleep 3
done