# Delivered Message Bulk-Sender Requirement Conformance Report (`kingii98/delivered-message-bulk-sender-requirement-conformance-report`) Actor

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

- **URL**: https://apify.com/kingii98/delivered-message-bulk-sender-requirement-conformance-report.md
- **Developed by:** [kingii98](https://apify.com/kingii98) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 run starteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## 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. 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.

```json
{
  "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.

| 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 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):

```json
{
  "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

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

# Actor input Schema

## `messages` (type: `array`):

1 to 100 raw RFC 5322 messages that a mailbox provider received (a '.eml' file or the Gmail 'Show original' download). Each item is an object with exactly one source: 'url' (an HTTPS URL of the raw message), 'record\_key' (a key-value store record, with an optional 'key\_value\_store' ID or name; without it the default store of the run is used) or 'eml\_base64' (the raw message as base64 text). A plain string item is also accepted: an https:// URL, a record key, or base64 text. Optional fields: 'stream\_label' (a name for the mail stream, for example 'weekly-digest'; the Actor compares each run with the last run for the same label) and 'message\_class' ('bulk' or 'transactional', default 'bulk'; a transactional message does not fail the unsubscribe rules). Limit: 2 MB for each message. The default is one sample message with synthetic headers.

## `trusted_authserv_ids` (type: `array`):

The Authentication-Results authserv-id values that the Actor trusts. An entry also matches its subdomains, so 'yahoo.com' matches 'atlas-production.v2.mail.yahoo.com'. Only the topmost field from a trusted receiver counts. Outlook writes its field without an authserv-id; the Actor reads it as 'mx.microsoft.com' when the message has an X-MS-Exchange-\* field. Default: mx.google.com, mx.microsoft.com, yahoo.com, aol.com.

## `alert_webhook_url` (type: `string`):

Optional. One HTTPS URL on port 443. When a stream label has a rule that passed in the last run and fails now, the Actor sends one JSON POST with all regressed streams of the run. Private and reserved hosts are refused and redirects are not followed.

## Actor input object example

```json
{
  "messages": [
    {
      "eml_base64": "RGVsaXZlcmVkLVRvOiBzZWVkLWluYm94QGV4YW1wbGUubmV0ClJlY2VpdmVkOiBieSAyMDAyOmEwNTo3MzAwOjFhMmI6YjA6MTJjOjNkNGU6NWY2MCB3aXRoIFNNVFAgaWQgc2FtcGxlMDE7CiAgICAgICAgVHVlLCAxNSBTZXAgMjAyNiAwMzowMDowMSAtMDcwMCAoUERUKQpBdXRoZW50aWNhdGlvbi1SZXN1bHRzOiBteC5nb29nbGUuY29tOwogICAgICAgZGtpbT1wYXNzIGhlYWRlci5pPUBleGFtcGxlLmNvbSBoZWFkZXIucz1zYW1wbGUgaGVhZGVyLmI9VTJGdGNHeGw7CiAgICAgICBzcGY9cGFzcyAoZ29vZ2xlLmNvbTogZG9tYWluIG9mIGJvdW5jZUBleGFtcGxlLmNvbSBkZXNpZ25hdGVzIDIwOS44NS4yMjAuNDEgYXMgcGVybWl0dGVkIHNlbmRlcikgc210cC5tYWlsZnJvbT1ib3VuY2VAZXhhbXBsZS5jb207CiAgICAgICBkbWFyYz1wYXNzIChwPVJFSkVDVCBzcD1SRUpFQ1QgZGlzPU5PTkUpIGhlYWRlci5mcm9tPWV4YW1wbGUuY29tClJlY2VpdmVkLVNQRjogcGFzcyAoZ29vZ2xlLmNvbTogZG9tYWluIG9mIGJvdW5jZUBleGFtcGxlLmNvbSBkZXNpZ25hdGVzIDIwOS44NS4yMjAuNDEgYXMgcGVybWl0dGVkIHNlbmRlcikgY2xpZW50LWlwPTIwOS44NS4yMjAuNDE7ClJlY2VpdmVkOiBmcm9tIG1haWwtc29yLWY0MS5nb29nbGUuY29tIChtYWlsLXNvci1mNDEuZ29vZ2xlLmNvbS4gWzIwOS44NS4yMjAuNDFdKQogICAgICAgIGJ5IG14Lmdvb2dsZS5jb20gd2l0aCBTTVRQUyBpZCBzYW1wbGUwMgogICAgICAgIGZvciA8c2VlZC1pbmJveEBleGFtcGxlLm5ldD4KICAgICAgICAodmVyc2lvbj1UTFMxXzMgY2lwaGVyPVRMU19BRVNfMjU2X0dDTV9TSEEzODQgYml0cz0yNTYvMjU2KTsKICAgICAgICBUdWUsIDE1IFNlcCAyMDI2IDAzOjAwOjAwIC0wNzAwIChQRFQpCkRLSU0tU2lnbmF0dXJlOiB2PTE7IGE9cnNhLXNoYTI1NjsgYz1yZWxheGVkL3JlbGF4ZWQ7IGQ9ZXhhbXBsZS5jb207IHM9c2FtcGxlOwogICAgICAgIGg9ZnJvbTp0bzpzdWJqZWN0OmRhdGU6bWVzc2FnZS1pZDpsaXN0LXVuc3Vic2NyaWJlOmxpc3QtdW5zdWJzY3JpYmUtcG9zdDsKICAgICAgICBiaD1VMkZ0Y0d4bElHSnZaSGtnYUdGemFDQnZibXg1TENCdWIzUWdZU0J5WldGc0lHOXVaUT09OwogICAgICAgIGI9VTJGdGNHeGxJSE5wWjI1aGRIVnlaU0J2Ym14NUxDQjBhR1VnYTJWNUlHbHpJRzV2ZENCd2RXSnNhWE5vWldRPQpGcm9tOiBFeGFtcGxlIFdlZWtseSA8bmV3c0BleGFtcGxlLmNvbT4KVG86IHNlZWQtaW5ib3hAZXhhbXBsZS5uZXQKU3ViamVjdDogU2FtcGxlIG1lc3NhZ2Ugd2l0aCBzeW50aGV0aWMgcmVjZWl2ZXIgaGVhZGVycwpEYXRlOiBUdWUsIDE1IFNlcCAyMDI2IDEwOjAwOjAwICswMDAwCk1lc3NhZ2UtSUQ6IDxzYW1wbGUtMDAwMUBleGFtcGxlLmNvbT4KTGlzdC1VbnN1YnNjcmliZTogPG1haWx0bzp1bnN1YnNjcmliZUBleGFtcGxlLmNvbT4sIDxodHRwczovL2V4YW1wbGUuY29tL3Vuc3Vic2NyaWJlPgpMaXN0LVVuc3Vic2NyaWJlLVBvc3Q6IExpc3QtVW5zdWJzY3JpYmU9T25lLUNsaWNrCk1JTUUtVmVyc2lvbjogMS4wCkNvbnRlbnQtVHlwZTogdGV4dC9wbGFpbjsgY2hhcnNldD11dGYtOAoKVGhpcyBzYW1wbGUgaGFzIHN5bnRoZXRpYyBoZWFkZXJzLiBUaGUgREtJTSBzaWduYXR1cmUgaXMgbm90IHJlYWwsIHNvIHRoZQppbmRlcGVuZGVudCB2ZXJpZmljYXRpb24gZ2l2ZXMgYW4gYWR2aXNvcnkgZmFpbHVyZS4K",
      "message_class": "bulk"
    }
  ],
  "trusted_authserv_ids": [
    "mx.google.com",
    "mx.microsoft.com",
    "yahoo.com",
    "aol.com"
  ]
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

## `messages` (type: `string`):

No description

## `authentication` (type: `string`):

No description

## `regressions` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "messages": [
        {
            "eml_base64": "RGVsaXZlcmVkLVRvOiBzZWVkLWluYm94QGV4YW1wbGUubmV0ClJlY2VpdmVkOiBieSAyMDAyOmEwNTo3MzAwOjFhMmI6YjA6MTJjOjNkNGU6NWY2MCB3aXRoIFNNVFAgaWQgc2FtcGxlMDE7CiAgICAgICAgVHVlLCAxNSBTZXAgMjAyNiAwMzowMDowMSAtMDcwMCAoUERUKQpBdXRoZW50aWNhdGlvbi1SZXN1bHRzOiBteC5nb29nbGUuY29tOwogICAgICAgZGtpbT1wYXNzIGhlYWRlci5pPUBleGFtcGxlLmNvbSBoZWFkZXIucz1zYW1wbGUgaGVhZGVyLmI9VTJGdGNHeGw7CiAgICAgICBzcGY9cGFzcyAoZ29vZ2xlLmNvbTogZG9tYWluIG9mIGJvdW5jZUBleGFtcGxlLmNvbSBkZXNpZ25hdGVzIDIwOS44NS4yMjAuNDEgYXMgcGVybWl0dGVkIHNlbmRlcikgc210cC5tYWlsZnJvbT1ib3VuY2VAZXhhbXBsZS5jb207CiAgICAgICBkbWFyYz1wYXNzIChwPVJFSkVDVCBzcD1SRUpFQ1QgZGlzPU5PTkUpIGhlYWRlci5mcm9tPWV4YW1wbGUuY29tClJlY2VpdmVkLVNQRjogcGFzcyAoZ29vZ2xlLmNvbTogZG9tYWluIG9mIGJvdW5jZUBleGFtcGxlLmNvbSBkZXNpZ25hdGVzIDIwOS44NS4yMjAuNDEgYXMgcGVybWl0dGVkIHNlbmRlcikgY2xpZW50LWlwPTIwOS44NS4yMjAuNDE7ClJlY2VpdmVkOiBmcm9tIG1haWwtc29yLWY0MS5nb29nbGUuY29tIChtYWlsLXNvci1mNDEuZ29vZ2xlLmNvbS4gWzIwOS44NS4yMjAuNDFdKQogICAgICAgIGJ5IG14Lmdvb2dsZS5jb20gd2l0aCBTTVRQUyBpZCBzYW1wbGUwMgogICAgICAgIGZvciA8c2VlZC1pbmJveEBleGFtcGxlLm5ldD4KICAgICAgICAodmVyc2lvbj1UTFMxXzMgY2lwaGVyPVRMU19BRVNfMjU2X0dDTV9TSEEzODQgYml0cz0yNTYvMjU2KTsKICAgICAgICBUdWUsIDE1IFNlcCAyMDI2IDAzOjAwOjAwIC0wNzAwIChQRFQpCkRLSU0tU2lnbmF0dXJlOiB2PTE7IGE9cnNhLXNoYTI1NjsgYz1yZWxheGVkL3JlbGF4ZWQ7IGQ9ZXhhbXBsZS5jb207IHM9c2FtcGxlOwogICAgICAgIGg9ZnJvbTp0bzpzdWJqZWN0OmRhdGU6bWVzc2FnZS1pZDpsaXN0LXVuc3Vic2NyaWJlOmxpc3QtdW5zdWJzY3JpYmUtcG9zdDsKICAgICAgICBiaD1VMkZ0Y0d4bElHSnZaSGtnYUdGemFDQnZibXg1TENCdWIzUWdZU0J5WldGc0lHOXVaUT09OwogICAgICAgIGI9VTJGdGNHeGxJSE5wWjI1aGRIVnlaU0J2Ym14NUxDQjBhR1VnYTJWNUlHbHpJRzV2ZENCd2RXSnNhWE5vWldRPQpGcm9tOiBFeGFtcGxlIFdlZWtseSA8bmV3c0BleGFtcGxlLmNvbT4KVG86IHNlZWQtaW5ib3hAZXhhbXBsZS5uZXQKU3ViamVjdDogU2FtcGxlIG1lc3NhZ2Ugd2l0aCBzeW50aGV0aWMgcmVjZWl2ZXIgaGVhZGVycwpEYXRlOiBUdWUsIDE1IFNlcCAyMDI2IDEwOjAwOjAwICswMDAwCk1lc3NhZ2UtSUQ6IDxzYW1wbGUtMDAwMUBleGFtcGxlLmNvbT4KTGlzdC1VbnN1YnNjcmliZTogPG1haWx0bzp1bnN1YnNjcmliZUBleGFtcGxlLmNvbT4sIDxodHRwczovL2V4YW1wbGUuY29tL3Vuc3Vic2NyaWJlPgpMaXN0LVVuc3Vic2NyaWJlLVBvc3Q6IExpc3QtVW5zdWJzY3JpYmU9T25lLUNsaWNrCk1JTUUtVmVyc2lvbjogMS4wCkNvbnRlbnQtVHlwZTogdGV4dC9wbGFpbjsgY2hhcnNldD11dGYtOAoKVGhpcyBzYW1wbGUgaGFzIHN5bnRoZXRpYyBoZWFkZXJzLiBUaGUgREtJTSBzaWduYXR1cmUgaXMgbm90IHJlYWwsIHNvIHRoZQppbmRlcGVuZGVudCB2ZXJpZmljYXRpb24gZ2l2ZXMgYW4gYWR2aXNvcnkgZmFpbHVyZS4K",
            "message_class": "bulk"
        }
    ],
    "trusted_authserv_ids": [
        "mx.google.com",
        "mx.microsoft.com",
        "yahoo.com",
        "aol.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("kingii98/delivered-message-bulk-sender-requirement-conformance-report").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "messages": [{
            "eml_base64": "RGVsaXZlcmVkLVRvOiBzZWVkLWluYm94QGV4YW1wbGUubmV0ClJlY2VpdmVkOiBieSAyMDAyOmEwNTo3MzAwOjFhMmI6YjA6MTJjOjNkNGU6NWY2MCB3aXRoIFNNVFAgaWQgc2FtcGxlMDE7CiAgICAgICAgVHVlLCAxNSBTZXAgMjAyNiAwMzowMDowMSAtMDcwMCAoUERUKQpBdXRoZW50aWNhdGlvbi1SZXN1bHRzOiBteC5nb29nbGUuY29tOwogICAgICAgZGtpbT1wYXNzIGhlYWRlci5pPUBleGFtcGxlLmNvbSBoZWFkZXIucz1zYW1wbGUgaGVhZGVyLmI9VTJGdGNHeGw7CiAgICAgICBzcGY9cGFzcyAoZ29vZ2xlLmNvbTogZG9tYWluIG9mIGJvdW5jZUBleGFtcGxlLmNvbSBkZXNpZ25hdGVzIDIwOS44NS4yMjAuNDEgYXMgcGVybWl0dGVkIHNlbmRlcikgc210cC5tYWlsZnJvbT1ib3VuY2VAZXhhbXBsZS5jb207CiAgICAgICBkbWFyYz1wYXNzIChwPVJFSkVDVCBzcD1SRUpFQ1QgZGlzPU5PTkUpIGhlYWRlci5mcm9tPWV4YW1wbGUuY29tClJlY2VpdmVkLVNQRjogcGFzcyAoZ29vZ2xlLmNvbTogZG9tYWluIG9mIGJvdW5jZUBleGFtcGxlLmNvbSBkZXNpZ25hdGVzIDIwOS44NS4yMjAuNDEgYXMgcGVybWl0dGVkIHNlbmRlcikgY2xpZW50LWlwPTIwOS44NS4yMjAuNDE7ClJlY2VpdmVkOiBmcm9tIG1haWwtc29yLWY0MS5nb29nbGUuY29tIChtYWlsLXNvci1mNDEuZ29vZ2xlLmNvbS4gWzIwOS44NS4yMjAuNDFdKQogICAgICAgIGJ5IG14Lmdvb2dsZS5jb20gd2l0aCBTTVRQUyBpZCBzYW1wbGUwMgogICAgICAgIGZvciA8c2VlZC1pbmJveEBleGFtcGxlLm5ldD4KICAgICAgICAodmVyc2lvbj1UTFMxXzMgY2lwaGVyPVRMU19BRVNfMjU2X0dDTV9TSEEzODQgYml0cz0yNTYvMjU2KTsKICAgICAgICBUdWUsIDE1IFNlcCAyMDI2IDAzOjAwOjAwIC0wNzAwIChQRFQpCkRLSU0tU2lnbmF0dXJlOiB2PTE7IGE9cnNhLXNoYTI1NjsgYz1yZWxheGVkL3JlbGF4ZWQ7IGQ9ZXhhbXBsZS5jb207IHM9c2FtcGxlOwogICAgICAgIGg9ZnJvbTp0bzpzdWJqZWN0OmRhdGU6bWVzc2FnZS1pZDpsaXN0LXVuc3Vic2NyaWJlOmxpc3QtdW5zdWJzY3JpYmUtcG9zdDsKICAgICAgICBiaD1VMkZ0Y0d4bElHSnZaSGtnYUdGemFDQnZibXg1TENCdWIzUWdZU0J5WldGc0lHOXVaUT09OwogICAgICAgIGI9VTJGdGNHeGxJSE5wWjI1aGRIVnlaU0J2Ym14NUxDQjBhR1VnYTJWNUlHbHpJRzV2ZENCd2RXSnNhWE5vWldRPQpGcm9tOiBFeGFtcGxlIFdlZWtseSA8bmV3c0BleGFtcGxlLmNvbT4KVG86IHNlZWQtaW5ib3hAZXhhbXBsZS5uZXQKU3ViamVjdDogU2FtcGxlIG1lc3NhZ2Ugd2l0aCBzeW50aGV0aWMgcmVjZWl2ZXIgaGVhZGVycwpEYXRlOiBUdWUsIDE1IFNlcCAyMDI2IDEwOjAwOjAwICswMDAwCk1lc3NhZ2UtSUQ6IDxzYW1wbGUtMDAwMUBleGFtcGxlLmNvbT4KTGlzdC1VbnN1YnNjcmliZTogPG1haWx0bzp1bnN1YnNjcmliZUBleGFtcGxlLmNvbT4sIDxodHRwczovL2V4YW1wbGUuY29tL3Vuc3Vic2NyaWJlPgpMaXN0LVVuc3Vic2NyaWJlLVBvc3Q6IExpc3QtVW5zdWJzY3JpYmU9T25lLUNsaWNrCk1JTUUtVmVyc2lvbjogMS4wCkNvbnRlbnQtVHlwZTogdGV4dC9wbGFpbjsgY2hhcnNldD11dGYtOAoKVGhpcyBzYW1wbGUgaGFzIHN5bnRoZXRpYyBoZWFkZXJzLiBUaGUgREtJTSBzaWduYXR1cmUgaXMgbm90IHJlYWwsIHNvIHRoZQppbmRlcGVuZGVudCB2ZXJpZmljYXRpb24gZ2l2ZXMgYW4gYWR2aXNvcnkgZmFpbHVyZS4K",
            "message_class": "bulk",
        }],
    "trusted_authserv_ids": [
        "mx.google.com",
        "mx.microsoft.com",
        "yahoo.com",
        "aol.com",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("kingii98/delivered-message-bulk-sender-requirement-conformance-report").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "messages": [
    {
      "eml_base64": "RGVsaXZlcmVkLVRvOiBzZWVkLWluYm94QGV4YW1wbGUubmV0ClJlY2VpdmVkOiBieSAyMDAyOmEwNTo3MzAwOjFhMmI6YjA6MTJjOjNkNGU6NWY2MCB3aXRoIFNNVFAgaWQgc2FtcGxlMDE7CiAgICAgICAgVHVlLCAxNSBTZXAgMjAyNiAwMzowMDowMSAtMDcwMCAoUERUKQpBdXRoZW50aWNhdGlvbi1SZXN1bHRzOiBteC5nb29nbGUuY29tOwogICAgICAgZGtpbT1wYXNzIGhlYWRlci5pPUBleGFtcGxlLmNvbSBoZWFkZXIucz1zYW1wbGUgaGVhZGVyLmI9VTJGdGNHeGw7CiAgICAgICBzcGY9cGFzcyAoZ29vZ2xlLmNvbTogZG9tYWluIG9mIGJvdW5jZUBleGFtcGxlLmNvbSBkZXNpZ25hdGVzIDIwOS44NS4yMjAuNDEgYXMgcGVybWl0dGVkIHNlbmRlcikgc210cC5tYWlsZnJvbT1ib3VuY2VAZXhhbXBsZS5jb207CiAgICAgICBkbWFyYz1wYXNzIChwPVJFSkVDVCBzcD1SRUpFQ1QgZGlzPU5PTkUpIGhlYWRlci5mcm9tPWV4YW1wbGUuY29tClJlY2VpdmVkLVNQRjogcGFzcyAoZ29vZ2xlLmNvbTogZG9tYWluIG9mIGJvdW5jZUBleGFtcGxlLmNvbSBkZXNpZ25hdGVzIDIwOS44NS4yMjAuNDEgYXMgcGVybWl0dGVkIHNlbmRlcikgY2xpZW50LWlwPTIwOS44NS4yMjAuNDE7ClJlY2VpdmVkOiBmcm9tIG1haWwtc29yLWY0MS5nb29nbGUuY29tIChtYWlsLXNvci1mNDEuZ29vZ2xlLmNvbS4gWzIwOS44NS4yMjAuNDFdKQogICAgICAgIGJ5IG14Lmdvb2dsZS5jb20gd2l0aCBTTVRQUyBpZCBzYW1wbGUwMgogICAgICAgIGZvciA8c2VlZC1pbmJveEBleGFtcGxlLm5ldD4KICAgICAgICAodmVyc2lvbj1UTFMxXzMgY2lwaGVyPVRMU19BRVNfMjU2X0dDTV9TSEEzODQgYml0cz0yNTYvMjU2KTsKICAgICAgICBUdWUsIDE1IFNlcCAyMDI2IDAzOjAwOjAwIC0wNzAwIChQRFQpCkRLSU0tU2lnbmF0dXJlOiB2PTE7IGE9cnNhLXNoYTI1NjsgYz1yZWxheGVkL3JlbGF4ZWQ7IGQ9ZXhhbXBsZS5jb207IHM9c2FtcGxlOwogICAgICAgIGg9ZnJvbTp0bzpzdWJqZWN0OmRhdGU6bWVzc2FnZS1pZDpsaXN0LXVuc3Vic2NyaWJlOmxpc3QtdW5zdWJzY3JpYmUtcG9zdDsKICAgICAgICBiaD1VMkZ0Y0d4bElHSnZaSGtnYUdGemFDQnZibXg1TENCdWIzUWdZU0J5WldGc0lHOXVaUT09OwogICAgICAgIGI9VTJGdGNHeGxJSE5wWjI1aGRIVnlaU0J2Ym14NUxDQjBhR1VnYTJWNUlHbHpJRzV2ZENCd2RXSnNhWE5vWldRPQpGcm9tOiBFeGFtcGxlIFdlZWtseSA8bmV3c0BleGFtcGxlLmNvbT4KVG86IHNlZWQtaW5ib3hAZXhhbXBsZS5uZXQKU3ViamVjdDogU2FtcGxlIG1lc3NhZ2Ugd2l0aCBzeW50aGV0aWMgcmVjZWl2ZXIgaGVhZGVycwpEYXRlOiBUdWUsIDE1IFNlcCAyMDI2IDEwOjAwOjAwICswMDAwCk1lc3NhZ2UtSUQ6IDxzYW1wbGUtMDAwMUBleGFtcGxlLmNvbT4KTGlzdC1VbnN1YnNjcmliZTogPG1haWx0bzp1bnN1YnNjcmliZUBleGFtcGxlLmNvbT4sIDxodHRwczovL2V4YW1wbGUuY29tL3Vuc3Vic2NyaWJlPgpMaXN0LVVuc3Vic2NyaWJlLVBvc3Q6IExpc3QtVW5zdWJzY3JpYmU9T25lLUNsaWNrCk1JTUUtVmVyc2lvbjogMS4wCkNvbnRlbnQtVHlwZTogdGV4dC9wbGFpbjsgY2hhcnNldD11dGYtOAoKVGhpcyBzYW1wbGUgaGFzIHN5bnRoZXRpYyBoZWFkZXJzLiBUaGUgREtJTSBzaWduYXR1cmUgaXMgbm90IHJlYWwsIHNvIHRoZQppbmRlcGVuZGVudCB2ZXJpZmljYXRpb24gZ2l2ZXMgYW4gYWR2aXNvcnkgZmFpbHVyZS4K",
      "message_class": "bulk"
    }
  ],
  "trusted_authserv_ids": [
    "mx.google.com",
    "mx.microsoft.com",
    "yahoo.com",
    "aol.com"
  ]
}' |
apify call kingii98/delivered-message-bulk-sender-requirement-conformance-report --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kingii98/delivered-message-bulk-sender-requirement-conformance-report"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/Rxr6mqMwfrdl6Y65F/builds/o0OlzhgainBfhbull/openapi.json
