> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Receiver-side deliverability: fold DMARC RUA reports and Postmaster reputation into your panel

> Wire your rua= mailbox and your Postmaster Tools and feedback-loop feeds into the two report analyzers, read the pass ratios and spoofing rankings they return, and decide when to advance your policy from none to quarantine to reject.

# Receiver-side deliverability loop

Everything Orbit checks before a send is a projection: DNS verification grades the
records you published, the Deliverability Lab scores the content you are about to
send, and test sends measure where synthetic mail lands. None of it tells you what
Google, Yahoo, and Microsoft actually did with your real traffic. Two stateless
analyzers close that gap:

* `POST /api/v1/email/dmarc/reports/analyze` folds the DMARC aggregate (RUA)
  reports receivers mail to your `rua=` address into authentication pass ratios,
  applied dispositions, and a ranked list of sources failing DMARC as your domain.
* `POST /api/v1/email/postmaster/reputation/analyze` folds Google Postmaster Tools
  TrafficStats snapshots and feedback-loop (FBL) complaint records into one
  reputation readout with a `healthy` flag and severity-ordered recommendations.

Both accept what you collected, compute an analysis, and persist nothing. Your
cron or connector owns the collection; the analyzers own the math.

**You will:**

1. [Understand which receiver-side feed answers which question](#1-why-receiver-side-data)
2. [Wire the RUA mailbox and submit report batches](#2-wire-the-rua-mailbox)
3. [Read the analysis and decide when to advance policy](#3-read-the-analysis)
4. [Fold in Postmaster Tools and feedback-loop data](#4-fold-in-postmaster-and-feedback-loop-data)
5. [Pair the loop with your sender-side gates](#5-pair-with-sender-side-gates-and-troubleshoot)

## 1. Why receiver-side data

A test send proves a path works; it is signed with the platform's test key, not
your domain's, and it samples a handful of seed inboxes. Your production traffic
is graded on evidence you can only get from the receivers themselves:

| Feed | Who reports it | What it answers | When it applies |
| - | - | - | - |
| DMARC RUA aggregates | Every receiver that accepted mail claiming your domain (Google, Microsoft, Yahoo, and the rest) | Which sources pass or fail SPF/DKIM alignment in the wild, and what disposition each receiver applied | Every sender domain, from the first day at `p=none` |
| Google Postmaster Tools | Google only | Domain and IP reputation bands, user-reported spam ratio, authentication success ratios, delivery-error breakdown at Gmail scale | Once you send meaningful volume to Gmail |
| Feedback loops (FBL) | Yahoo, Microsoft, AOL | Which campaigns drew complaints, and when | When you need complaint attribution, not just complaint suppression |

RUA is the universal feed: it exists for any domain that publishes a `rua=` tag,
at any volume, and it is the only evidence that makes advancing
`none → quarantine → reject` defensible. Postmaster adds Google's reputation
grading on top, and FBL tells you why. Orbit already acts on the sender side of
complaints (hard bounces, exhausted soft bounces, complaints, and unsubscribes
are suppressed automatically), but suppression hides the campaign that caused the
complaint; the FBL fold surfaces it.

The mechanics of the RUA report format itself are covered in
[DMARC aggregate reports (RUA): ingest and analysis](https://docs.orbit.devotel.io/concepts/dmarc-aggregate-ingest.md);
this guide is the operations loop around it.

## 2. Wire the RUA mailbox

**1. Point `rua=` at a mailbox you control.** The address lives in your `_dmarc`
TXT record next to the policy:

```text theme={null}
_dmarc.example.com  TXT  "v=DMARC1; p=none; rua=mailto:dmarc@example.com"
```

Publish it from day one, at `p=none`. Receivers only start mailing aggregates
after they see the tag, and a capture mailbox for infrastructure reports should
never double as a human inbox. The record-level rules (one record, never two;
keep `rua=` through every policy step) are in
[Publishing the rua= record](https://docs.orbit.devotel.io/concepts/dmarc-aggregate-ingest.md).

**2. Run a cron or connector against that mailbox.** Orbit does not read your
mailbox for you; the analyzers are stateless by design, so collection is a
tenant-owned control. A daily job is the natural cadence because providers roll
up one UTC day per report. For each attachment, classify the transport shape:

| What you pulled out of the mailbox | `encoding` to declare |
| - | - |
| The `.xml.gz` / `application/gzip` attachment as mailed, base64'd | `gzip` |
| An uncompressed MIME attachment, base64'd | `base64` |
| XML you already extracted or pasted | `utf8` (the default) |

**3. POST batches of up to 20 reports.** Each `content` is capped at 1 MiB as
submitted, and a single request may mix encodings. Corrupt payloads (bad base64,
malformed XML, a gzip stream that will not decode) are skipped and tallied; they
never fail the batch.

> [tab] curl
>
> ```bash theme={null}
> curl -X POST "https://api.orbit.devotel.io/api/v1/email/dmarc/reports/analyze" \
>   -H "X-API-Key: dv_live_sk_..." \
>   -H "Content-Type: application/json" \
>   -d '{
>     "reports": [
>       { "content": "H4sIAAAAAAAAA+2ca3Pb...", "encoding": "gzip" },
>       { "content": "PD94bWwgdmVyc2lvbj0iMS4w...", "encoding": "base64" },
>       { "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>...", "encoding": "utf8" }
>     ],
>     "topSpoofingSources": 25
>   }'
> ```

> [tab] Node SDK
>
> The generated client exposes the operation by its id; the raw-request cookbook
> in [SDK raw requests](https://docs.orbit.devotel.io/guides/sdk-raw-request-cookbook.md) shows the escape hatch
> when you need full control over the wire format:
>
> ```ts theme={null}
> import { OrbitClient } from "@devotel/sdk-node";
>
> const orbit = new OrbitClient({ apiKey: process.env.ORBIT_API_KEY });
>
> const { data } = await orbit.createEmailDmarcReportsAnalyze({
>   reports: attachments.map((a) => ({
>     content: a.base64Body,
>     encoding: a.mime === "application/gzip" ? "gzip" : "base64",
>   })),
>   topSpoofingSources: 25,
> });
>
> console.log(data.submittedReports, data.parsedReports, data.skippedReports);
> ```

**4. Read the tally before the analysis.** The response reports an honest
split of what you submitted versus what was actually analyzed:

```json theme={null}
{
  "data": {
    "submittedReports": 3,
    "parsedReports": 2,
    "skippedReports": 1,
    "summary": {
      "spfSuccessRatio": 0.97,
      "dkimSuccessRatio": 0.99,
      "dmarcSuccessRatio": 0.96,
      "dispositions": { "none": 46980, "quarantine": 1105, "reject": 125 }
    }
  },
  "meta": { "request_id": "req_xyz789", "timestamp": "2026-10-30T00:00:00Z" }
}
```

A nonzero `skippedReports` is not an error; it is the decoder telling you a
payload was unreadable. If it stays high, your connector is misclassifying an
encoding or forwarding truncated attachments.

## 3. Read the analysis

Work through the response in this order:

1. **The tally.** `submittedReports` should match what your connector fetched
   for the window; `parsedReports` plus `skippedReports` equals it.
2. **The alignment ratios.** `dmarcSuccessRatio` is the headline: the share of
   received volume that passed DMARC (either aligned SPF or aligned DKIM, per
   RFC 7489). `spfSuccessRatio` and `dkimSuccessRatio` tell you which mechanism
   is carrying your mail when the headline dips.
3. **The dispositions.** The none/quarantine/reject counts are what receivers
   *applied* to your mail, not what you published. A block of failures already
   at `quarantine` or `reject` means enforcement is catching something; a spike
   of `none`-disposition failures at unfamiliar sources means it is flowing
   unimpeded.
4. **The ranked spoofing sources.** Every entry is one of two things: a
   legitimate sender you never aligned (a marketing tool, a helpdesk, a payroll
   service sending with your header-from but outside your SPF and DKIM), or an
   actual spoofer. Onboard the first; enforce against the second. The optional
   `topSpoofingSources` request bound (1 to 200) controls how long that ranked
   list may be.
5. **The recommendation.** Given your published policy and the observed pass
   ratios over the sample, the analyzer returns `advance`, `hold`, or
   `investigate`, with a confidence level and the specific blocker to fix first
   (a weak SPF-alignment rate, a weak DKIM-alignment rate, or a top failing
   source to identify).

**When to advance policy.** Start at `p=none` and stay there until the DMARC
pass ratio is healthy over a meaningful sample and every failing source on the
ranking is either onboarded or accepted as a spoofer. Then move to
`p=quarantine` with a `pct` sample (for example `pct=25`), raise `pct` while the
ratio holds, and finish at `p=reject`. Re-run the same analysis after every
republish; each batch of aggregates confirms the stricter policy is only
catching mail that deserves it. Advance on a `hold` or `investigate`
recommendation and you turn a monitoring problem into a blocking problem.

## 4. Fold in Postmaster and feedback-loop data

RUA tells you how your mail authenticated; Postmaster Tools tells you how Google
grades you regardless, and FBL records tell you who complained. Pull both on the
same cron that collects RUA:

* **Google Postmaster Tools** — fetch the daily `TrafficStats` resources for
  your domain from the Postmaster Tools API and pass them raw in `google`
  (up to 200 snapshots): domain and IP reputation, the user-reported spam
  ratio, SPF/DKIM/DMARC success ratios, and the delivery-error breakdown.
* **Feedback loops** — pass complaint records from Yahoo, Microsoft, and AOL
  in `feedback` (up to 200).

At least one record across the two arrays is required; an empty body returns
422\.

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/email/postmaster/reputation/analyze" \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "google": [
      {
        "domain": "example.com",
        "date": "2026-10-28",
        "domainReputation": "HIGH",
        "spamRatio": 0.0008,
        "ipReputation": { "high": 0.97, "medium": 0.02, "low": 0.01, "bad": 0 }
      }
    ],
    "feedback": [
      { "domain": "example.com", "date": "2026-10-28", "complaints": 3 }
    ]
  }'
```

The response folds every snapshot into one readout:

```json theme={null}
{
  "data": {
    "healthy": false,
    "providers": ["google", "feedback"],
    "domains": ["example.com"],
    "domainReputation": "MEDIUM",
    "spamRate": 0.004,
    "complaintRate": 0.0009,
    "spfSuccessRatio": 0.98,
    "dkimSuccessRatio": 0.97,
    "dmarcSuccessRatio": 0.96,
    "ipReputation": { "high": 0.91, "medium": 0.06, "low": 0.02, "bad": 0.01 },
    "deliveryErrors": [{ "type": "rateLimitExceeded", "ratio": 0.002 }],
    "problemIps": ["198.51.100.9"],
    "recommendations": [
      "Identify the source at 198.51.100.9 before raising volume: it drives the bad IP-reputation bucket.",
      "Complaint rate is inside the healthy band; hold current list-growth practice."
    ]
  },
  "meta": { "request_id": "req_xyz789", "timestamp": "2026-10-30T00:00:00Z" }
}
```

Read it top to bottom: the `healthy` flag first, then `recommendations` in the
order returned (most severe first), then the ratios and reputation bands to see
which input drove the verdict. The `perProvider` breakdown separates Google's
view from the FBL view when the two disagree, and `problemIps` names the
addresses dragging the IP-reputation buckets so you can check them against your
own sending infrastructure before assuming an external spoofer.

## 5. Pair with sender-side gates, and troubleshoot

The receiver-side loop never replaces the sender-side gates; it completes them.
Before volume, the order of operations is still:

1. Verify DNS and keep it green under **Channels → Email → Domains**, as walked
   through in the
   [email lifecycle guide](https://docs.orbit.devotel.io/guides/email-lifecycle-guide.md#1-domain-setup-on-day-one).
   A drifted DKIM record shows up in RUA aggregates a day later as a collapsing
   `dkimSuccessRatio`.
2. Score content in the
   [Deliverability Lab](https://docs.orbit.devotel.io/guides/campaign-deliverability-lab.md) before each send.
   The Lab grades what you are about to send; the analyzers grade what you
   already sent. When inbox placement dips, the Lab tells you whether content
   changed and the RUA fold tells you whether authentication or reputation did.

When something looks wrong, the common causes in order:

* **422 on an empty batch.** `reports` accepts 1 to 20 payloads and the
  Postmaster body needs at least one record in `google` or `feedback`. A 422
  right after a quiet weekend usually means the connector fetched an empty
  mailbox and POSTed anyway; have it skip empty windows.
* **403 on a valid key.** Both analyzers require an owner or admin role. A key
  minted for a member or viewer role fails here even when it sends fine.
* **422 on an oversize payload.** Each `content` is capped at 1 MiB as
  submitted. A report over the cap should be split at the collector (submit
  fewer reports per call) rather than truncated.
* **High `skippedReports`.** The payload decoded as nothing readable: an
  encoding mislabeled (a gzip stream declared `utf8`), a truncated attachment,
  or a gzip stream past the platform's decompression ceiling. The tally is the
  diagnosis; fix the connector, resubmit the window.

## Next steps

* [DMARC aggregate reports (RUA): ingest and analysis](https://docs.orbit.devotel.io/concepts/dmarc-aggregate-ingest.md) — the report format, the transport shapes, and the pipeline in depth.
* [Campaign Deliverability Lab](https://docs.orbit.devotel.io/guides/campaign-deliverability-lab.md) — the pre-send counterpart of this loop.
* [Email channel lifecycle](https://docs.orbit.devotel.io/guides/email-lifecycle-guide.md) — domain verification, warm-up, and suppression in order.
* [Channels → Email reference](https://docs.orbit.devotel.io/channels/email.md) — the full email surface, including the endpoint contracts used above.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.