Skip to content

Overview

The secbytech API lets you programmatically test email deliverability, scan domain configurations, flatten SPF records, and monitor domain health. No API key required for most endpoints — protected by Proof-of-Work (PoW) challenges and rate limits.

Base URL: https://mailcheck.secbytech.com

Authentication

Most POST endpoints require a Proof-of-Work (PoW) solution instead of an API key. The flow:

# 1. Request a challenge
curl -s "https://mailcheck.secbytech.com/api/v1/pow-challenge?scope=delivery-test"
# Returns: {"challenge":"...","difficulty":...,"algorithm":"sha256","expires_at":...}

# 2. Solve the challenge (find nonce where SHA256(challenge+nonce) has N leading zeros)

# 3. Include solution in your POST request body
{"domain":"example.com","pow_challenge":"...","pow_nonce":"..."}

Monitor endpoints use a dashboard_token (returned at registration) passed as ?token= query parameter or Authorization: Bearer <api_token> header.

Endpoints

GET /api/v1/pow-challenge

Get a Proof-of-Work challenge. Required before calling any PoW-protected POST endpoint.

Query parameters:

ParamRequiredDescription
scopeYesOne of: delivery-test, outbound-test, monitor-create, monitor-patch, spf-flatten

Response (200):

{
  "challenge": "a1b2c3...",
  "difficulty": 4,
  "algorithm": "sha256",
  "expires_at": 1715300000
}

Rate limit: 10/hour per IP

Errors: 400 missing/invalid scope, 429 rate limit

curl -s "https://mailcheck.secbytech.com/api/v1/pow-challenge?scope=delivery-test"

POST /api/v1/delivery-test

Create a new email delivery test. Returns a unique token and test email address.

Request body:

FieldTypeDescription
pow_challengestringChallenge from /api/v1/pow-challenge
pow_noncestringSolution nonce

Response (201):

{
  "token": "a1b2c3d4e5f67890",
  "test_address": "a1b2c3d4e5f67890@mailcheck.secbytech.com",
  "expires_in": 3600
}

Rate limit: 10/hour per IP

Errors: 429 rate limit, 400 invalid PoW

curl -s -X POST https://mailcheck.secbytech.com/api/v1/delivery-test \
  -H 'Content-Type: application/json' \
  -d '{"pow_challenge":"...","pow_nonce":"..."}'

GET /api/v1/delivery-test/{token}

Check test status and retrieve JSON results.

StatusHTTP CodeDescription
Pending202Email not yet received. Keep polling.
Complete200Full results with score, breakdown, and analysis.
Expired410Token expired (1 hour TTL).
Not found404Invalid token.

Complete response includes:

{
  "status": "complete",
  "score": 8.5,
  "rating": "Good",
  "breakdown": {"auth": 10.0, "spam": 9.0, "blacklists": 10.0, "infra": 6.0},
  "auth": {"spf": "pass", "dkim": "pass", "dmarc": "pass"},
  "blacklists": {"listed": 0, "checked": 46},
  "ptr": {"ptr": "mail.example.com", "fcrdns": true},
  "tls": {"used": true, "version": "TLSv1.3"},
  "spam": {"score": 1.2, "is_spam": false, "rules": [...]},
  "delivery_timing": {"total_seconds": 3.2, "hop_count": 3}
}
curl -s https://mailcheck.secbytech.com/api/v1/delivery-test/a1b2c3d4e5f67890

GET /api/v1/delivery-test/{token}/report

Get the full HTML report. Returns Content-Type: text/html.

Self-contained HTML with all analysis, educational content, and fix suggestions.

curl -s https://mailcheck.secbytech.com/api/v1/delivery-test/a1b2c3d4e5f67890/report > report.html

GET /api/v1/delivery-test/{token}/share

Create a shareable link for a completed test result. Creates a share record (state-changing GET).

Response (201):

{
  "share_url": "/share/abc123def456",
  "share_id": "abc123def456",
  "expires_in": 604800
}

Rate limit: 10/hour per IP

Errors: 404 test not found or not complete, 429 rate limit

curl -s https://mailcheck.secbytech.com/api/v1/delivery-test/a1b2c3d4e5f67890/share

POST /api/v1/outbound-test

Start a domain configuration scan (SPF, DKIM, DMARC, MTA-STS, DANE, BIMI, blacklists).

Request body:

FieldTypeRequiredDescription
domainstringYesDomain to scan (max 253 chars)
pow_challengestringYesChallenge from /api/v1/pow-challenge (scope: outbound-test)
pow_noncestringYesSolution nonce

Response (201):

{
  "token": "a1b2c3d4e5f67890",
  "status": "running"
}

Rate limit: 3/hour per IP. Max 4 concurrent scans globally.

Errors: 400 invalid domain or private TLD, 429 rate limit or too many concurrent scans

curl -s -X POST https://mailcheck.secbytech.com/api/v1/outbound-test \
  -H 'Content-Type: application/json' \
  -d '{"domain":"example.com","pow_challenge":"...","pow_nonce":"..."}'

GET /api/v1/outbound-test/{token}

Poll outbound scan results.

StatusHTTP CodeDescription
Running202Scan in progress. Retry-After: 5s.
Complete200Full scan results with findings.
Not found404Invalid token.

Complete response includes:

{
  "status": "complete",
  "domain": "example.com",
  "findings": [{"id": "spf_missing", "severity": "high", ...}],
  "dns_records": {"spf": "...", "dmarc": "...", "mx": [...]},
  "modules_run": 8,
  "modules_failed": 0
}
curl -s https://mailcheck.secbytech.com/api/v1/outbound-test/a1b2c3d4e5f67890

GET /report/outbound/{token}

Get the full HTML outbound scan report. Returns Content-Type: text/html.

Errors: 404 not found, 202 scan still running

curl -s https://mailcheck.secbytech.com/report/outbound/a1b2c3d4e5f67890 > outbound-report.html

POST /api/tools/spf-flatten

Flatten an SPF record by resolving all includes/redirects into explicit IP addresses.

Request body:

FieldTypeRequiredDescription
domainstringYesDomain whose SPF record to flatten
pow_challengestringYesChallenge (scope: spf-flatten)
pow_noncestringYesSolution nonce

Response (200):

{
  "original": "v=spf1 include:_spf.google.com ~all",
  "flattened": "v=spf1 ip4:209.85.128.0/17 ip4:... ~all",
  "char_count": 312,
  "lookup_count": 4,
  "ip4": ["209.85.128.0/17", ...],
  "ip6": ["2607:f8b0:4000::/36", ...],
  "ip4_qualified": [{"addr": "209.85.128.0/17", "qualifier": "+"}],
  "ip6_qualified": [{"addr": "2607:f8b0:4000::/36", "qualifier": "+"}],
  "include_map": {"_spf.google.com": {"ip4": [...], "ip6": [...]}},
  "all_qualifier": "~all"
}

Rate limit: 10/hour per IP

Errors: 400 invalid/missing domain, 429 rate limit

curl -s -X POST https://mailcheck.secbytech.com/api/tools/spf-flatten \
  -H 'Content-Type: application/json' \
  -d '{"domain":"example.com","pow_challenge":"...","pow_nonce":"..."}'

POST /api/v1/monitor

Register a domain for continuous health monitoring. Requires DNS verification after creation.

Request body:

FieldTypeRequiredDescription
domainstringYesDomain to monitor (max 253 chars)
emailstringYesAlert email address (max 254 chars)
dkim_selectorstringNoDKIM selector to track (max 63 chars, lowercase alphanumeric + hyphens)
pow_challengestringYesChallenge (scope: monitor-create)
pow_noncestringYesSolution nonce

Response (201):

{
  "status": "pending_verification",
  "domain": "example.com",
  "instructions": "Add a DNS TXT record: _mailcheck-verify.example.com with value: secbytech-verify=abc123",
  "verify_url": "/api/v1/monitor/verify/abc123",
  "unsubscribe_token": "unsub-token-xyz",
  "dashboard_token": "dash-token-xyz"
}

Rate limit: 10/hour per IP

Errors: 400 invalid domain/email/selector, 429 rate limit

curl -s -X POST https://mailcheck.secbytech.com/api/v1/monitor \
  -H 'Content-Type: application/json' \
  -d '{"domain":"example.com","email":"admin@example.com","pow_challenge":"...","pow_nonce":"..."}'

GET /api/v1/monitor/verify/{token}

Verify domain ownership by checking the DNS TXT record. Call after adding the TXT record from the registration response.

Response (200) — success:

{
  "status": "verified",
  "domain": "example.com",
  "api_token": "your-api-token-for-patch",
  "dashboard_url": "/dashboard/example.com?token=dash-token-xyz"
}

Response (200) — already verified:

{"status": "already_verified", "domain": "example.com"}

Rate limit: 10/hour per IP

Errors: 404 token not found, 400 DNS record not found or mismatch, 429 rate limit

curl -s https://mailcheck.secbytech.com/api/v1/monitor/verify/abc123

GET /api/v1/monitor/{domain}

Get monitoring status, recent checks, and alerts for a verified domain. Requires authentication.

Authentication: ?token=<dashboard_token> or session cookie

Response (200):

{
  "domain": "example.com",
  "status": "active",
  "score": 9.2,
  "last_check_at": "2026-05-09T12:00:00Z",
  "check_interval_hours": 24,
  "checks": [{"checked_at": "...", "status": "ok", "score": 9.2}],
  "alerts": [{"alert_type": "spf_changed", "message": "...", "created_at": "..."}]
}

Errors: 404 domain not found/not verified, 403 access denied

curl -s "https://mailcheck.secbytech.com/api/v1/monitor/example.com?token=dash-token-xyz"

POST /api/v1/monitor/{domain}/check

Trigger an immediate health check for a monitored domain.

Authentication: ?token=<dashboard_token> or session cookie

Response (200):

{
  "domain": "example.com",
  "score": 9.2,
  "rating": "Excellent",
  "breakdown": {"spf": 10, "dkim": 10, "dmarc": 8, "mx": 10, "tls": 9},
  "changes": null
}

Rate limit: 1/hour per IP, 1/hour per domain

Errors: 403 access denied, 429 already triggered recently, 504 check timed out

curl -s -X POST "https://mailcheck.secbytech.com/api/v1/monitor/example.com/check?token=dash-token-xyz"

PATCH /api/v1/monitor/{domain}

Update monitor settings (currently: DKIM selector). Requires the api_token returned at verification.

Authentication: Authorization: Bearer <api_token> header (preferred) or api_token in request body

Request body:

FieldTypeRequiredDescription
dkim_selectorstringYesNew DKIM selector (max 63 chars)
pow_challengestringYesChallenge (scope: monitor-patch)
pow_noncestringYesSolution nonce

Response (200):

{"status": "updated", "domain": "example.com", "dkim_selector": "selector2"}

Errors: 401 api_token missing, 403 invalid token, 400 invalid selector, 404 domain not found

curl -s -X PATCH https://mailcheck.secbytech.com/api/v1/monitor/example.com \
  -H 'Authorization: Bearer your-api-token' \
  -H 'Content-Type: application/json' \
  -d '{"dkim_selector":"selector2","pow_challenge":"...","pow_nonce":"..."}'

POST /api/donate

Create a Stripe checkout session for a donation.

Request body:

FieldTypeRequiredDescription
amountintegerYesAmount in cents (e.g., 500 = €5.00)
currencystringNoCurrency code (default: "eur")
display_namestringNoName to show on supporters wall (max 100 chars)
show_on_wallbooleanNoWhether to display on supporters page (default: false)

Response (200):

{"checkout_url": "https://checkout.stripe.com/..."}

Rate limit: 5/hour per IP

Errors: 400 invalid amount/JSON, 429 rate limit, 503 donations not configured

curl -s -X POST https://mailcheck.secbytech.com/api/donate \
  -H 'Content-Type: application/json' \
  -d '{"amount":500,"currency":"eur","display_name":"Alice","show_on_wall":true}'

GET /supporters

Get the public list of supporters who opted to be displayed.

Response (200):

{"supporters": [{"display_name": "Alice", "amount": 500, "currency": "eur", "created_at": "..."}]}

Rate limit: None


curl -s https://mailcheck.secbytech.com/supporters

GET /health

Health check endpoint. Returns basic status (unauthenticated) or detailed checks (with API key).

Response (200) — unauthenticated:

{"status": "ok"}

Response (503): Database unreachable

Rate limit: None


curl -s https://mailcheck.secbytech.com/health

GET /status

Aggregate test statistics. No sensitive data exposed.

Response (200):

{
  "total_tests": 1234,
  "pending": 5,
  "complete": 1200,
  "expired": 29
}

Rate limit: None


curl -s https://mailcheck.secbytech.com/status

GET /share/{share_id}

View a shared delivery test report. Returns HTML.

Errors: 404 share not found or expired

curl -s https://mailcheck.secbytech.com/share/abc123def456 > shared-report.html

Rate Limits

EndpointLimit
POST /api/v1/delivery-test10/hour per IP
POST /api/v1/outbound-test3/hour per IP + max 4 concurrent
POST /api/tools/spf-flatten10/hour per IP
POST /api/v1/monitor10/hour per IP
POST /api/v1/monitor/{domain}/check1/hour per IP + 1/hour per domain
POST /api/donate5/hour per IP
GET /api/v1/pow-challenge10/hour per IP
GET endpoints (results, health, status, supporters)No limit

Rate limit responses return 429 with {"error": "rate limit exceeded"}.

Data Retention

Delivery test token validity1 hour (must send email within this window)
Delivery test results24 hours after completion
Shared reports7 days
Outbound scan results24 hours
Monitor dataRetained while subscription active

CI/CD Integration

GitHub Actions

# .github/workflows/email-test.yml
name: Email Deliverability Test
on: [push]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Create test
        id: create
        run: |
          RESP=$(curl -s -X POST https://mailcheck.secbytech.com/api/v1/delivery-test \
            -H 'Content-Type: application/json' -d '{"pow_challenge":"...","pow_nonce":"..."}')
          echo "token=$(echo $RESP | jq -r .token)" >> $GITHUB_OUTPUT
          echo "address=$(echo $RESP | jq -r .test_address)" >> $GITHUB_OUTPUT

      - name: Send test email
        run: echo "Send email to ${{ steps.create.outputs.address }}"

      - name: Wait and check results
        run: |
          for i in $(seq 1 24); do
            RESP=$(curl -s https://mailcheck.secbytech.com/api/v1/delivery-test/${{ steps.create.outputs.token }})
            STATUS=$(echo $RESP | jq -r .status)
            if [ "$STATUS" = "complete" ]; then
              SCORE=$(echo $RESP | jq -r .score)
              echo "Score: $SCORE"
              if (( $(echo "$SCORE < 7.0" | bc -l) )); then
                echo "::error::Score $SCORE below threshold"
                exit 1
              fi
              exit 0
            fi
            sleep 5
          done
          echo "::warning::Test timed out"
          exit 1

SDK Examples

Complete examples showing how to use the API programmatically. Each script gets a PoW challenge, solves it, submits a request, and polls for results.

Python (stdlib only)

#!/usr/bin/env python3
"""Email delivery test using the secbytech API."""
import hashlib, json, time, urllib.request

BASE = "https://mailcheck.secbytech.com"

def solve_pow(challenge, difficulty):
    """Find nonce where SHA256(challenge+nonce) has `difficulty` leading zero bits."""
    required_hex = difficulty // 4
    remaining = difficulty % 4
    nonce = 0
    while True:
        h = hashlib.sha256(f"{challenge}{nonce}".encode()).hexdigest()
        if h[:required_hex] == "0" * required_hex:
            if remaining == 0 or int(h[required_hex], 16) < (1 << (4 - remaining)):
                return str(nonce)
        nonce += 1

def api_post(path, body):
    req = urllib.request.Request(
        f"{BASE}{path}",
        data=json.dumps(body).encode(),
        headers={"Content-Type": "application/json"},
    )
    with urllib.request.urlopen(req) as r:
        return json.loads(r.read())

def api_get(path):
    with urllib.request.urlopen(f"{BASE}{path}") as r:
        return json.loads(r.read())

# 1. Get PoW challenge
challenge_resp = api_get("/api/v1/pow-challenge?scope=delivery-test")
challenge = challenge_resp["challenge"]
difficulty = challenge_resp["difficulty"]

# 2. Solve it
print(f"Solving PoW (difficulty={difficulty})...")
nonce = solve_pow(challenge, difficulty)
print(f"Solved: nonce={nonce}")

# 3. Create delivery test
test = api_post("/api/v1/delivery-test", {
    "pow_challenge": challenge,
    "pow_nonce": nonce,
})
print(f"Send your email to: {test['test_address']}")
print(f"Token: {test['token']}")

# 4. Poll for results
while True:
    result = api_get(f"/api/v1/delivery-test/{test['token']}")
    if result["status"] == "complete":
        print(f"Score: {result['score']}/10 ({result['rating']})")
        print(f"Auth: SPF={result['auth']['spf']}, DKIM={result['auth']['dkim']}, DMARC={result['auth']['dmarc']}")
        break
    print("Waiting for email...")
    time.sleep(5)

Node.js (v18+ with native fetch)

#!/usr/bin/env node
/** Email delivery test using the secbytech API. */
const crypto = require("crypto");

const BASE = "https://mailcheck.secbytech.com";

function solvePow(challenge, difficulty) {
  const requiredHex = Math.floor(difficulty / 4);
  const remaining = difficulty % 4;
  const prefix = "0".repeat(requiredHex);
  let nonce = 0;
  while (true) {
    const hash = crypto.createHash("sha256").update(`${challenge}${nonce}`).digest("hex");
    if (hash.startsWith(prefix)) {
      if (remaining === 0 || parseInt(hash[requiredHex], 16) < (1 << (4 - remaining))) {
        return String(nonce);
      }
    }
    nonce++;
  }
}

async function main() {
  // 1. Get PoW challenge
  const challengeResp = await fetch(`${BASE}/api/v1/pow-challenge?scope=delivery-test`).then(r => r.json());
  const { challenge, difficulty } = challengeResp;

  // 2. Solve it
  console.log(`Solving PoW (difficulty=${difficulty})...`);
  const nonce = solvePow(challenge, difficulty);
  console.log(`Solved: nonce=${nonce}`);

  // 3. Create delivery test
  const test = await fetch(`${BASE}/api/v1/delivery-test`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ pow_challenge: challenge, pow_nonce: nonce }),
  }).then(r => r.json());

  console.log(`Send your email to: ${test.test_address}`);
  console.log(`Token: ${test.token}`);

  // 4. Poll for results
  while (true) {
    const result = await fetch(`${BASE}/api/v1/delivery-test/${test.token}`).then(r => r.json());
    if (result.status === "complete") {
      console.log(`Score: ${result.score}/10 (${result.rating})`);
      console.log(`Auth: SPF=${result.auth.spf}, DKIM=${result.auth.dkim}, DMARC=${result.auth.dmarc}`);
      break;
    }
    console.log("Waiting for email...");
    await new Promise(r => setTimeout(r, 5000));
  }
}

main().catch(console.error);
← Back to Tools