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:
| Param | Required | Description |
|---|---|---|
scope | Yes | One 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:
| Field | Type | Description |
|---|---|---|
pow_challenge | string | Challenge from /api/v1/pow-challenge |
pow_nonce | string | Solution 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.
| Status | HTTP Code | Description |
|---|---|---|
| Pending | 202 | Email not yet received. Keep polling. |
| Complete | 200 | Full results with score, breakdown, and analysis. |
| Expired | 410 | Token expired (1 hour TTL). |
| Not found | 404 | Invalid 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:
| Field | Type | Required | Description |
|---|---|---|---|
domain | string | Yes | Domain to scan (max 253 chars) |
pow_challenge | string | Yes | Challenge from /api/v1/pow-challenge (scope: outbound-test) |
pow_nonce | string | Yes | Solution 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.
| Status | HTTP Code | Description |
|---|---|---|
| Running | 202 | Scan in progress. Retry-After: 5s. |
| Complete | 200 | Full scan results with findings. |
| Not found | 404 | Invalid 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:
| Field | Type | Required | Description |
|---|---|---|---|
domain | string | Yes | Domain whose SPF record to flatten |
pow_challenge | string | Yes | Challenge (scope: spf-flatten) |
pow_nonce | string | Yes | Solution 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:
| Field | Type | Required | Description |
|---|---|---|---|
domain | string | Yes | Domain to monitor (max 253 chars) |
email | string | Yes | Alert email address (max 254 chars) |
dkim_selector | string | No | DKIM selector to track (max 63 chars, lowercase alphanumeric + hyphens) |
pow_challenge | string | Yes | Challenge (scope: monitor-create) |
pow_nonce | string | Yes | Solution 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:
| Field | Type | Required | Description |
|---|---|---|---|
dkim_selector | string | Yes | New DKIM selector (max 63 chars) |
pow_challenge | string | Yes | Challenge (scope: monitor-patch) |
pow_nonce | string | Yes | Solution 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:
| Field | Type | Required | Description |
|---|---|---|---|
amount | integer | Yes | Amount in cents (e.g., 500 = €5.00) |
currency | string | No | Currency code (default: "eur") |
display_name | string | No | Name to show on supporters wall (max 100 chars) |
show_on_wall | boolean | No | Whether 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
| Endpoint | Limit |
|---|---|
| POST /api/v1/delivery-test | 10/hour per IP |
| POST /api/v1/outbound-test | 3/hour per IP + max 4 concurrent |
| POST /api/tools/spf-flatten | 10/hour per IP |
| POST /api/v1/monitor | 10/hour per IP |
| POST /api/v1/monitor/{domain}/check | 1/hour per IP + 1/hour per domain |
| POST /api/donate | 5/hour per IP |
| GET /api/v1/pow-challenge | 10/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 validity | 1 hour (must send email within this window) |
| Delivery test results | 24 hours after completion |
| Shared reports | 7 days |
| Outbound scan results | 24 hours |
| Monitor data | Retained 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);