Email Preflight API & Scraper
The Email Preflight API returns pre-send QA for HTML email as clean JSON.
🤖 Using an AI assistant? Copy this link into ChatGPT / Claude / Cursor — it reads every endpoint and parameter instantly and tells you if this API fits your use case.
The primary audit_html endpoint returns HTML validity and warnings, message size with Gmail-clip risk and accessibility issues such as missing alt text — everything you would check before hitting send. You can also test CSS support across clients, compute a spam score, check links and images, verify DNS auth and render a basic preview. It is built for ESPs, marketing tools and QA pipelines that need to catch broken email before it ships. One ReefAPI key, one shared credit pool, the standard envelope.
Three email engines, three different questions
email-preflight, email-health and validate look adjacent and answer different things: the message, the sending domain, and one recipient address. The overlap is real and narrow, and it is worth knowing exactly where it is. Every figure below is from live calls made against the same test template, github.com and [email protected].
| What you are asking | Engine and action | Measured |
|---|---|---|
| Is this HTML broken before I send it | email-preflight audit_html | 4 images, 3 missing alt, one table at width=700 flagged for mobile, one tracking pixel, score 74 grade C |
| Which CSS will break in Outlook | email-preflight css_support | position, display:flex and border-radius all high_risk across 12 clients, from a bundled Can-I-Email snapshot v1.0.4 dated 2026-05-19 |
| Does the copy read as spam | email-preflight spam_score | 7.3 verdict likely_spam over 7 rules; a plain rewrite of the same message scored 0.0 clean_signal |
| Do the links and images resolve | email-preflight check_links / check_images | 4 links: 1 ok, 1 broken (real 404), 2 unverifiable; 4 images: 3 missing alt, 1 over http, 2 without dimensions |
| Is my sending domain authenticated, fast | email-preflight dns_auth | github.com scored 90.0 grade A; returns 9 keys (domain, score, spf, dkim, dmarc, mx, summary, issues, source) |
| Is my sending domain authenticated, in full | email-health check | identical score object, 90.0 grade A, but 14 keys adding bimi, mta_sts, tls_rpt, dnssec, dane and the blacklist detail |
| Is this one address deliverable | validate email | [email protected] returned valid true, deliverable true, score 95, has_mx true and the MX exchange itself |
| What will it look like | email-preflight preview_basic | returned available:false with reason "web-capture engine is not reachable for rendering"; the other actions are unaffected |
dns_auth is a documented wrapper, not a second implementation: its own payload names source "email-health inner-call" and reused_engine "email-health". It drops the six standalone section objects but keeps their scores in score.breakdown (mx 20, spf 22, dmarc 22, dkim 16, blacklist 10, mta_sts 4, dnssec 3, tls_rpt 2, bimi 1, totalling 100) and their booleans in summary. Call dns_auth for a pre-send go/no-go, email-health when you need the records themselves.
Real request and response JSON
Captured from the indexed primary action, audit_html, on .
{
"method": "POST",
"url": "https://api.reefapi.com/email-preflight/v1/audit_html",
"headers": {
"x-api-key": "$REEF_KEY",
"content-type": "application/json"
},
"body": {
"html": "<html><body><img src='https://x.test/a.png'><a href='https://x.test'>hi</a></body></html>"
}
}{
"ok": true,
"meta": {
"api": "email-preflight",
"endpoint": "audit_html",
"mode": "live",
"latency_ms": 3.4,
"record_count": 5,
"bytes": 0,
"cache_hit": false,
"proxy_tier_note": "none"
},
"data": {
"html_validity": {
"parse_ok": true,
"warnings": []
},
"size": {
"bytes": 89,
"gmail_clip_risk": false,
"limit_bytes": 102000
},
"accessibility": {
"images_total": 1,
"missing_alt": 1,
"missing_alt_srcs": [
"https://x.test/a.png"
],
"issues": [
"1 image(s) without alt text",
"no lang attribute on the document (screen-reader language hint)"
]
},
"structure": {
"has_doctype": false,
"has_table_layout": false,
"uses_div_layout": false,
"heading_count": 0,
"script_tags": 0,
"link_count": 1,
"image_count": 1
},
"mobile_width_risks": [
"[redacted-phone]"
],
"dark_mode": {
"has_meta_color_scheme": false,
"hints": [
"no <meta name=color-scheme> — clients may auto-invert your colors"
]
},
"tracking_pixels": [],
"external_assets": [],
"unsubscribe": {
"list_unsubscribe_header_present": false,
"has_unsubscribe_link": false,
"has_unsubscribe_text": false
},
"text_part": {
"present": false,
"recommended": true
},
"recommendations": [
{
"severity": "error",
"code": "MISSING_ALT",
"message": "1 image(s) lack alt text — add descriptive alt for accessibility + when images are blocked."
},
{
"severity": "error",
"code": "NO_UNSUB",
"message": "No unsubscribe link/text found — required by CAN-SPAM/CASL and critical for deliverability."
},
{
"severity": "warning",
"code": "NO_DOCTYPE",
"message": "No <!DOCTYPE> — declare one (XHTML 1.0 Transitional is the email-safe default) for predictable rendering."
}
],
"score": {
"value": 46,
"grade": "F"
}
}
}What the Email Preflight API does
| Action | Description | Concrete use case | Key params |
|---|---|---|---|
| audit_html | Full pre-send structure + accessibility + deliverability lint of an HTML email: missing alt text, broken structure, mobile-width risks, dark-mode hints, tracking pixels, external assets, List-Unsubscribe check, Gmail-clip size budget, and prioritized recommendations. Pure local compute (no network). | Platform and DevOps teams call audit_html to get full pre-send structure + accessibility + deliverability lint of an HTML email. | html, text_part, base_url, subject, check_links |
| css_support | Email-client CSS/HTML compatibility map for the template (Can-I-Email data, MIT): which CSS features used in the HTML break or degrade in Outlook/Gmail/Apple Mail/Yahoo and friends. Pure local compute (offline snapshot). | Security and supply-chain teams call css_support to get email-client CSS/HTML compatibility map for the template (Can-I-Email data, MIT). | html |
| spam_score | Deterministic spam-SIGNAL score for the email content (caps ratio, image-to-text ratio, spam phrases, missing unsubscribe, raw-IP links…). HONEST: a content signal score, NOT an inbox-placement prediction; SpamAssassin/Rspamd sidecar = the calibrated upgrade (see docs). Pure local compute. | Developer-tool builders call spam_score to get deterministic spam-SIGNAL score for the email content (caps ratio, image-to-text ratio, spam…. | html, subject, text_part |
| check_links | Resolve every link in the email through url-resolver (SSRF-guarded): final URL, redirect chain, status, final domain. Bot-403/429 = `unverifiable` (honest — not marked broken). Inner-calls url-resolver per link. | AI-agent developers call check_links to resolve every link in the email through url-resolver (SSRF-guarded). | html, base_url |
| check_images | Audit every <img> in the email: alt text presence, dimensions declared, src scheme/format, http-vs-https, and (best-effort) reachability via url-resolver. Pure-compute by default; reachability adds sub-calls. | Platform and DevOps teams call check_images to get audit every <img> in the email. | html, base_url, check_reachable |
| dns_auth | Sender-domain email-authentication summary (SPF/DKIM/DMARC/MX + deliverability grade) via an inner-call to the email-health engine — REUSE, not re-implemented here (email-health owns DNS auth; this is a thin preflight wrapper). | Security and supply-chain teams call dns_auth to get sender-domain email-authentication summary (SPF/DKIM/DMARC/MX + deliverability grade) via an…. | domain, dkim_selector, include_blacklist |
| preview_basic | Basic visual preview: render the HTML to a PNG via web-capture's browser sandbox. HONEST: a browser render, NOT a real email-client render (no Outlook/Word engine). Degrades gracefully if web-capture is not deployed. | Developer-tool builders call preview_basic to get basic visual preview. | html, width |
Call audit_html from your stack
curl -X POST https://api.reefapi.com/email-preflight/v1/audit_html \
-H "x-api-key: $REEF_KEY" \
-H "content-type: application/json" \
-d '{"html":"<html><body><img src='https://x.test/a.png'><a href='https://x.test'>hi</a></body></html>"}'import requests
r = requests.post(
"https://api.reefapi.com/email-preflight/v1/audit_html",
headers={"x-api-key": REEF_KEY},
json={
"html": "<html><body><img src='https://x.test/a.png'><a href='https://x.test'>hi</a></body></html>"
},
)
print(r.json()["data"])const res = await fetch("https://api.reefapi.com/email-preflight/v1/audit_html", {
method: "POST",
headers: {
"x-api-key": process.env.REEF_KEY,
"content-type": "application/json",
},
body: JSON.stringify({
"html": "<html><body><img src='https://x.test/a.png'><a href='https://x.test'>hi</a></body></html>"
}),
});
const { ok, data, meta, error } = await res.json();Ask your MCP-connected assistant: call reefapi.email-preflight.audit_html with {"html":"<html><body><img src='https://x.test/a.png'><a href='https://x.test'>hi</a></body></html>"}.Who uses this API and why
- ESPs and marketing tools call audit_html to catch broken markup and clipping risk before a send.
- QA pipelines use spam_score and check_links to flag deliverability and broken-link problems.
- Accessibility checks use audit_html to ensure images have alt text and the email is readable.
Questions developers ask before integrating
Should I call dns_auth or email-health?
dns_auth when you want one pre-send verdict, email-health when you want the evidence. Measured on github.com they produced the identical score object, 90.0 grade A with the same nine-section breakdown, but email-health returned 14 top-level keys and dns_auth returned 9. What dns_auth drops is the standalone bimi, mta_sts, tls_rpt, dnssec, dane and blacklist objects; their contribution is still visible in score.breakdown and their booleans in summary. If you need the SPF mechanism tree, the DKIM key bits or the DNSBL zone list, go to email-health.
Why did the same domain grade A on one call and B on the next?
Because include_blacklist defaults to false and the skipped section is still scored. With the default, github.com returned blacklist 10 out of 10 with the note "clean across 15 DNSBLs", summary.blacklisted false, and a total of 90.0 grade A. With include_blacklist true the same domain returned blacklist 6 out of 10 with the note "1 listing(s)", summary.blacklisted true, and 86.0 grade B. The fast default therefore awards a full clean blacklist score without having run the lookup. Pass include_blacklist true before you report a reputation result to anyone.
How is this different from validate?
Different object entirely. validate's email action takes one address and answers whether that mailbox is worth sending to: a measured [email protected] returned syntax_valid true, has_mx true with the exchange github-com.mail.protection.outlook.com, is_disposable false, is_role_account false, is_free_provider false, deliverable true and score 95. email-preflight never looks at a recipient. It takes the message body, or the sending domain, and answers whether the thing you are about to send is well formed. A typical pipeline uses validate on the list, dns_auth once on the sender, and audit_html once per template.
I used alt="" on a decorative image and it was still counted as missing.
Yes, an empty alt attribute counts as missing in both audit_html and check_images. In a measured run the image carrying alt="" appeared in accessibility.missing_alt_srcs and came back from check_images as alt "" with has_alt false. That is stricter than the HTML specification, where an empty alt is the correct marking for a decorative image. If you use that convention, subtract those images from missing_alt yourself before acting on the MISSING_ALT recommendation.
unsubscribe.list_unsubscribe_header_present is always false. Why?
Because List-Unsubscribe is an SMTP header and audit_html only ever sees the HTML body, so there is nowhere for it to be true. In our runs it was false while has_unsubscribe_link and has_unsubscribe_text were both true from an in-body link. Treat that field as a checklist reminder for your sending code rather than a finding about the template, and read the two has_unsubscribe_* fields for what the HTML actually contains.
What does verdict "unverifiable" mean on a link?
That we could not prove the link is dead, so we refuse to call it broken. Of four links in a measured template, one returned verdict ok, one returned verdict broken with a real HTTP 404, and two returned unverifiable: one with reason "host does not resolve" and error_code NOT_FOUND, the other reporting a resolution timeout against a slow target, with the response spelling out that a timeout is not proof the link is broken, and error_code INTERNAL. The response note spells out the rule: broken means a real status of 400 or above from the target, while anti-bot responses, timeouts and transport failures all land in unverifiable. Do not fail a campaign on unverifiable.
check_links reported status 206 for a link that works. Is that right?
Yes. Reachability checks use a ranged request, so a healthy target commonly answers 206 Partial Content rather than 200. A measured github.com link returned status 206 with verdict ok and redirect_count 0, and a measured image returned reachable true with http_status 206. Key on verdict for links and on reachable for images rather than testing status == 200 yourself. Images that were never attempted come back with reachable null, which is not the same as false.
Is the spam score a prediction that I will land in spam?
No, and the response says so in threshold_note: "score >= 5.0 ~ likely spam (SpamAssassin convention); this is a deterministic content SIGNAL, not an inbox-placement prediction." It is rule-based and repeatable. A deliberately awful test message scored 7.3 with verdict likely_spam over 7 rules including FREE_MONEY 1.5, CAPS_HEAVY 1.5 at a measured 36 percent all-caps words, URGENCY 1.0, MANY_EXCLAIM 1.0 and IMG_RATIO 1.0 for 4 images against 25 words; a plain rewrite scored 0.0 with verdict clean_signal and zero rules. Note that audit_html does not include this block, so spam_score is a separate call.
What is the Email Preflight API?
Email Preflight API is a ReefAPI endpoint group for email preflight It returns live JSON through POST requests under /email-preflight/v1.
Is the Email Preflight API free to try?
Yes. ReefAPI starts with 1,000 free credits, no card required. Email Preflight calls use the same shared credit balance as every other ReefAPI engine.
Do I need an Email Preflight login or account?
No login to Email Preflight is needed for the API response. You call ReefAPI with your x-api-key header, and the playground can run live examples before you create a production key.
How fresh is the Email Preflight data?
The page example is captured from a live audit_html call, and production requests fetch live data through ReefAPI rather than a static sample.
How many credits does the Email Preflight API use?
Email Preflight actions currently cost 1-3 credits per successful call. Failed or blocked calls are free, and all APIs draw from one credit pool.
Can I call Email Preflight from an AI assistant or MCP client?
Yes. Connect ReefAPI once through MCP and your assistant can call email-preflight actions with the same key, credit pool and JSON envelope used by normal REST requests.