Delivered Message Bulk-Sender Requirement Conformance Report avatar

Delivered Message Bulk-Sender Requirement Conformance Report

Pricing

from $4.00 / 1,000 run starteds

Go to Apify Store
Delivered Message Bulk-Sender Requirement Conformance Report

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

kingii98

Maintained by Community

Actor 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

FieldTypeDefaultDescription
messagesarrayone sample message1 to 100 raw messages. Limit: 2 MB for each message.
trusted_authserv_idsarraymx.google.com, mx.microsoft.com, yahoo.com, aol.comThe receivers that the Actor trusts. An entry also matches its subdomains.
alert_webhook_urlstringnoneOne 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. Add key_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/plain or binary) or a JSON object with eml_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) or transactional. A transactional message gets not_applicable for 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

  1. Receiver field. The Actor uses the topmost Authentication-Results field with a trusted authserv-id. Earlier hops can write false fields, so the Actor ignores lower fields. It reads an ARC-Authentication-Results field only when no trusted Authentication-Results field exists. Outlook writes its field without an authserv-id. The Actor reads that field as mx.microsoft.com only when the message also has an X-MS-Exchange-* field.
  2. Sending IP. The Actor takes the IP from the receiver field (the Gmail designates ... as permitted sender comment, the Outlook sender IP is comment, or smtp.remote-ip / policy.iprev). Then it tries the topmost Received-SPF field, then the Received field.
  3. Receiver hop. The topmost Received field that names the sending IP in its from clause is the hop where the receiver took the message. The Actor looks for a TLS marker there (ESMTPS, SMTPS, version=TLS..., using TLS...).
  4. DKIM. For each DKIM-Signature field (at most 5), the Actor gets the key over DNS and verifies the signature with RSA-SHA256 or Ed25519-SHA256.
  5. 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.

RuleSeverityBulk onlyPasses when
authentication_results_foundrequirednoA trusted receiver field is present and has SPF, DKIM or DMARC results.
spf_passrequirednoThe receiver reports spf=pass.
dkim_passrequirednoThe receiver reports dkim=pass for at least one signature.
dmarc_passrequirednoThe receiver reports dmarc=pass.
dmarc_record_publishedrequirednoOne DMARC record exists for the From domain or its organizational domain.
spf_or_dkim_alignedrequirednoA passing SPF domain or DKIM d= domain aligns with the From domain.
spf_alignedadvisorynoThe passing smtp.mailfrom domain aligns with the From domain (aspf from the DMARC record).
dkim_alignedadvisorynoA passing DKIM d= domain aligns with the From domain (adkim from the DMARC record).
dkim_signature_verifiedadvisorynoAn aligned signature verifies against the DKIM key that is in DNS now.
list_unsubscribe_httpsrequiredyesList-Unsubscribe holds an HTTPS URI.
list_unsubscribe_post_one_clickrequiredyesList-Unsubscribe-Post is List-Unsubscribe=One-Click.
unsubscribe_headers_dkim_signedrequiredyesA valid DKIM signature has both unsubscribe fields in h= (RFC 8058 section 4).
unsubscribe_host_dns_tlsrequiredyesThe unsubscribe host resolves only to public addresses and passes a TLS handshake with certificate checks on port 443.
tls_last_external_hoprequirednoThe receiver hop shows TLS.
sending_ip_fcrdnsrequirednoA PTR name of the sending IP resolves back to the same IP.
message_id_presentrequirednoThe message has one Message-ID field.
date_presentrequirednoThe 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 and smtp.mailfrom domain, the receiver DKIM results with d= 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, the requirements table and the verdict.
  • 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 lists regressedRules.
  • 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.

EventUnitPrice (USD)
run-startedOne Actor run.0.004
message-checkedOne 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-flaggedOne 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

EventCountPrice (USD)Cost (USD)
run-started10.0040.0040
message-checked50.010.0500
stream-regression-flagged10.010.0100
Total0.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.

MessagesRegressionsCost without a limit (USD)Cost with a USD 2 limit (USD)Messages checked
100.01400.01401
510.06400.06405
10001.00401.0040100
1001002.00401.9940100

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. authResultsStatus is unparsed when a trusted field has no SPF, DKIM or DMARC result that the Actor can read.