Delivered Message Bulk-Sender Requirement Conformance Report
Pricing
from $4.00 / 1,000 run starteds
Delivered Message Bulk-Sender Requirement Conformance Report
Prove Gmail, Yahoo and Outlook bulk-sender conformance from a message they received. Give it raw .eml messages. It reads the receiver Authentication-Results, verifies each DKIM signature, checks SPF and DKIM alignment, the DMARC record, one-click unsubscr
Pricing
from $4.00 / 1,000 run starteds
Rating
0.0
(0)
Developer
kingii98
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
6 days ago
Last modified
Categories
Share
Delivered Message Bulk-Sender Requirement Conformance Report from Raw Headers
Prove from a delivered message that your mail conforms to the Gmail, Yahoo and Outlook bulk-sender rules.
Send a seed message to a test inbox. Download the raw message (.eml, or
Show original > Download original in Gmail). Give it to this Actor. The Actor
reads the headers that the mailbox provider wrote, verifies each DKIM signature
again, and checks the DNS records. You get one requirement table for each message
with pass, fail or not_applicable for each rule, and an overall verdict.
Give each message a stream_label, for example weekly-digest. The Actor then
keeps the last requirement table for each label. When a rule that passed in the
last run fails now, you get a stream-regression record and, if you set a
webhook, one JSON POST.
Who uses it
- A SaaS team that sends sign-up, password-reset or digest mail to Gmail, Yahoo and Outlook users.
- An email marketer or an email service reseller that must prove conformance for each client stream.
- A CI job that runs after each template change, sending provider change or DNS change.
What the Actor does not do
- It does not send mail and it does not read a mailbox. You supply the message.
- It does not send a GET or POST to the unsubscribe URL, because such a request can unsubscribe a real recipient. It resolves the URL host over DNS and makes one TLS handshake to a public address of that host. It sends no HTTP data.
- It does not check the spam complaint rate. That data needs Google Postmaster Tools with authentication.
- It does not store message bodies. The output holds header field values, results and SHA-256 hashes only.
Input
| Field | Type | Default | Description |
|---|---|---|---|
messages | array | one sample message | 1 to 100 raw messages. Limit: 2 MB for each message. |
trusted_authserv_ids | array | mx.google.com, mx.microsoft.com, yahoo.com, aol.com | The receivers that the Actor trusts. An entry also matches its subdomains. |
alert_webhook_url | string | none | One HTTPS URL. The Actor sends one JSON POST when a stream regresses. |
Each item of messages is an object with exactly one source:
url: an HTTPS URL of the raw message (port 443, no credentials, at most 3 redirects, private and reserved hosts are refused).record_key: a key-value store record. Addkey_value_store(a store ID or name) to read from a store that you own. The record can hold the raw message (message/rfc822,text/plainor binary) or a JSON object witheml_base64.eml_base64: the raw message as base64 text.
Optional fields for each item:
stream_label: the name of the mail stream (1 to 100 characters).message_class:bulk(default) ortransactional. A transactional message getsnot_applicablefor the four unsubscribe rules.
A plain string item is also accepted: an https:// URL, a record key, or base64
text.
{"messages": [{"url": "https://example.com/seeds/digest.eml", "stream_label": "weekly-digest"},{"record_key": "reset-mail", "key_value_store": "my-seed-messages","stream_label": "password-reset", "message_class": "transactional"}],"alert_webhook_url": "https://hooks.example.com/mail-regression"}
With an empty input, the Actor checks one sample message with synthetic headers.
The sample gets the verdict pass with one advisory failure, because its DKIM
signature is not real.
How the Actor reads a message
- Receiver field. The Actor uses the topmost
Authentication-Resultsfield with a trusted authserv-id. Earlier hops can write false fields, so the Actor ignores lower fields. It reads anARC-Authentication-Resultsfield only when no trustedAuthentication-Resultsfield exists. Outlook writes its field without an authserv-id. The Actor reads that field asmx.microsoft.comonly when the message also has anX-MS-Exchange-*field. - Sending IP. The Actor takes the IP from the receiver field (the Gmail
designates ... as permitted sendercomment, the Outlooksender IP iscomment, orsmtp.remote-ip/policy.iprev). Then it tries the topmostReceived-SPFfield, then theReceivedfield. - Receiver hop. The topmost
Receivedfield that names the sending IP in itsfromclause is the hop where the receiver took the message. The Actor looks for a TLS marker there (ESMTPS,SMTPS,version=TLS...,using TLS...). - DKIM. For each
DKIM-Signaturefield (at most 5), the Actor gets the key over DNS and verifies the signature with RSA-SHA256 or Ed25519-SHA256. - DNS. The Actor looks up the DMARC record of the From domain (with the organizational-domain fallback), the PTR and forward records of the sending IP, and the A and AAAA records of the unsubscribe host. All DNS lookups go to public DNS-over-HTTPS resolvers (Google, then Cloudflare).
Rules
A required rule sets the verdict. An advisory rule is reported in
advisoryFailures but does not change the verdict.
| Rule | Severity | Bulk only | Passes when |
|---|---|---|---|
authentication_results_found | required | no | A trusted receiver field is present and has SPF, DKIM or DMARC results. |
spf_pass | required | no | The receiver reports spf=pass. |
dkim_pass | required | no | The receiver reports dkim=pass for at least one signature. |
dmarc_pass | required | no | The receiver reports dmarc=pass. |
dmarc_record_published | required | no | One DMARC record exists for the From domain or its organizational domain. |
spf_or_dkim_aligned | required | no | A passing SPF domain or DKIM d= domain aligns with the From domain. |
spf_aligned | advisory | no | The passing smtp.mailfrom domain aligns with the From domain (aspf from the DMARC record). |
dkim_aligned | advisory | no | A passing DKIM d= domain aligns with the From domain (adkim from the DMARC record). |
dkim_signature_verified | advisory | no | An aligned signature verifies against the DKIM key that is in DNS now. |
list_unsubscribe_https | required | yes | List-Unsubscribe holds an HTTPS URI. |
list_unsubscribe_post_one_click | required | yes | List-Unsubscribe-Post is List-Unsubscribe=One-Click. |
unsubscribe_headers_dkim_signed | required | yes | A valid DKIM signature has both unsubscribe fields in h= (RFC 8058 section 4). |
unsubscribe_host_dns_tls | required | yes | The unsubscribe host resolves only to public addresses and passes a TLS handshake with certificate checks on port 443. |
tls_last_external_hop | required | no | The receiver hop shows TLS. |
sending_ip_fcrdns | required | no | A PTR name of the sending IP resolves back to the same IP. |
message_id_present | required | no | The message has one Message-ID field. |
date_present | required | no | The message has one Date field. |
dkim_signature_verified is advisory because the check uses the key that is in
DNS now. After a key rotation, an old seed message fails this check. Send a new
seed message after each key change.
Output
The dataset has four record types:
message: one record for each checked message. It holds the stream label, the receiver authserv-id, the From domain, the SPF result andsmtp.mailfromdomain, the receiver DKIM results withd=domains, the DMARC result and policy, the result of the independent check for each DKIM signature, SPF and DKIM alignment, the DMARC record, the unsubscribe fields and host result, TLS on the receiver hop, the sending IP and its reverse DNS,Message-ID,Date, hashes, therequirementstable and theverdict.message-error: an item that the Actor did not check (input-rejected,not-loaded,not-a-message,check-error). It is not charged.stream-regression: one record for each stream label with a rule that passed in the last run and fails now. It listsregressedRules.run-summary: counts, the result for each stream, and the webhook result.
Example message record (shortened):
{"recordType": "message","messageIndex": 0,"source": "url:https://example.com/seeds/digest.eml","streamLabel": "weekly-digest","verdict": "fail","failedRules": ["list_unsubscribe_post_one_click"],"advisoryFailures": [],"authResultsStatus": "parsed","receiverAuthservId": "mx.google.com","fromDomain": "news.example.com","spfResult": "pass","spfMailfromDomain": "bounce.news.example.com","dmarcResult": "pass","dmarcPolicyReported": "reject","dkimSignatures": [{"domain": "news.example.com", "selector": "s1", "result": "pass","receiverResult": "pass", "coversUnsubscribeHeaders": true, "alignedWithFrom": true}],"requirements": [{"rule": "spf_pass", "status": "pass", "severity": "required","detail": "mx.google.com: spf=pass."}]}
The run ends SUCCEEDED when a message fails, when a message does not load and
when the input is refused. The status message gives the counts. Only a real
malfunction ends the run FAILED.
Stream state
The Actor keeps the state in the named key-value store
bulk-sender-conformance-state of your account, one record for each label. The
record holds the merged rule statuses of the last run for the label: when a run
has more than one message for a label, fail wins over pass. A run without
labels reads and writes no state.
Webhook payload
{"event": "stream-regression","runId": "abc123","checkedAt": "2026-09-17T08:00:00+00:00","regressions": [{"streamLabel": "weekly-digest", "regressedRules": ["tls_last_external_hop"],"previousRunAt": "2026-08-17T08:00:00+00:00", "verdict": "fail"}]}
The Actor sends at most one POST for each run. A failed delivery is shown in the run summary and the status message.
Pricing
This Actor uses pay-per-event pricing.
| Event | Unit | Price (USD) |
|---|---|---|
run-started | One Actor run. | 0.004 |
message-checked | One raw message that was parsed, verified and scored against the rules. A message that did not load or is not a message is not charged. | 0.01 |
stream-regression-flagged | One stream label with a rule that passed in the last run and fails now, however many rules regressed. | 0.01 |
What a monthly check of 5 mail streams costs
| Event | Count | Price (USD) | Cost (USD) |
|---|---|---|---|
run-started | 1 | 0.004 | 0.0040 |
message-checked | 5 | 0.01 | 0.0500 |
stream-regression-flagged | 1 | 0.01 | 0.0100 |
| Total | 0.0640 |
Without a regression, the same run costs USD 0.054.
How the size of the run changes the cost
The default maximum charge for each run is USD 2.
| Messages | Regressions | Cost without a limit (USD) | Cost with a USD 2 limit (USD) | Messages checked |
|---|---|---|---|---|
| 1 | 0 | 0.0140 | 0.0140 | 1 |
| 5 | 1 | 0.0640 | 0.0640 | 5 |
| 100 | 0 | 1.0040 | 1.0040 | 100 |
| 100 | 100 | 2.0040 | 1.9940 | 100 |
Before each message, the Actor makes sure that the charge limit covers the
message. When the limit is reached, the other messages are not checked and the
run summary shows messagesSkippedByChargeLimit. A regression that the limit
does not cover is not flagged, and the Actor keeps the old state for that label,
so the next run flags it. For 100 messages that each have their own label and
all regress, set the maximum charge to USD 2.01 or more.
Limits
- 1 to 100 messages, 2 MB for each message, 20 trusted authserv-ids.
- 5 DKIM signatures and 5 PTR names for each message.
- Download time limit 20 s for the full download, with redirects. TLS handshake timeout 10 s. Webhook time limit 10 s for the full call; the Actor reads at most 4 KB of the webhook reply.
- The Actor does not follow the unsubscribe URL and checks only port 443.
- Receiver header formats change. The Actor reads the Gmail, Yahoo, AOL and
Outlook formats of 2026.
authResultsStatusisunparsedwhen a trusted field has no SPF, DKIM or DMARC result that the Actor can read.