Accept the cost of publishing, so the record is offered
Records that a person was shown what tightening this domain's DMARC policy will cost them, and agreed to it.
Some of a domain's failing mail is its own, broken on the way by a
forwarder the customer does not control. That mail is not a fault they
can fix, so it does not block the verdict — and precisely because
nothing is judging it, nothing else would ever make them look at it.
This endpoint is what makes it visible: a domain in the
ready_pending_acknowledgement state has met every criterion and is
offered no record until the cost is accepted.
There is no request body and no figure to send. The server takes the
cost from the domain's current assessment — the same volumes, the same
named senders and the same sentence readiness.cost.sentence returns —
and freezes them onto the domain's timeline. A client-supplied number
would record what the client chose to send rather than what the customer
was shown, and the record would then say somebody accepted a cost they
never saw.
It records a person, so a client-credentials token is refused, the
same way decideDomainAuthSource refuses one. Re-authorize with a
user-delegated token.
Only the pending state has anything to acknowledge. A domain with
blocking criteria outstanding, one already ready, and one that would
cost nothing to publish all return 422.
An acknowledgement covers ONE RUNG. Accepting a p=quarantine cost is
not accepting a p=reject one — same mail, different consequence — so
the next rung asks again. So does a cost that has since grown by more
than half AND by at least 100 messages over the window.
Reversible in the only sense that matters: it publishes nothing and changes no DNS. It is classified non-destructive and a test-environment credential may call it. What it unlocks is a string for a person to paste; no endpoint on this API publishes DNS.
Path parameters
domain string required The domain, by NAME (acme.com). A UUID is also accepted. Matched case-insensitively, and scoped to the calling org — a domain belonging to another org is a 404, never a 403.
Responses
Errors follow the RFC 7807 problem format — see the error reference.
acknowledgement object required One recorded acceptance of a publishing cost, read back exactly as it was frozen.
EVERY FIGURE HERE IS WHAT WAS ON THE SCREEN, not what is true now. The volumes move daily and this record must not: it exists to answer "what did they actually agree to?" from one row, a year later. sentence is the wording that was shown, verbatim — "will go to a spam folder" and "will be refused" are different agreements about the same mail, and only the wording tells them apart afterwards.
Fields of acknowledgement:
acknowledged_at string<date-time> required actor_email string required The person who gave it. There is always one: a client-credentials token is refused, because a machine cannot agree on somebody's behalf to lose some of their mail.
target_policy string enum required The rung it was about. Accepting a quarantine cost is not accepting a reject one, so the next rung asks again.
One of: ``, none, quarantine, reject
relay_broken_messages integer<int64> required The forwarding volume accepted, over window_days. Staleness is measured against this figure and no other.
relay_broken_sources integer required relay_broken_label string required reviewed_messages integer<int64> required reviewed_sources integer required sources array<string> required The senders named on the screen, BY NAME rather than by id: a source group can be re-keyed, merged or dropped, and an id that resolves to nothing is worse than no id because it looks like an answer.
sources_truncated boolean required True when more senders were shown than are listed here.
window_days integer required sentence string required readiness object required The DMARC policy ladder for one domain.
Fields of readiness:
current_policy string enum required One of: no_record, none, quarantine, reject
target_policy string enum required One of: no_record, none, quarantine, reject
ready boolean required True only when every BLOCKING criterion is met and no cost the verdict stopped blocking on is outstanding.
ready_pending_acknowledgement boolean required Every criterion is met and a cost the verdict stopped blocking on has not been accepted. A DISTINCT STATE, not a seventh unmet criterion: the domain has done everything it can do, and what is outstanding is a sentence to agree to. ready stays false and record_to_publish is empty; cost.sentence is what is owed.
cost object | null optional What publishing would actually cost, or null when it would cost nothing. Read it beside record_to_publish.
record_diff array<object> | null optional What publishing record_to_publish would change about the record live in DNS now, field by field. Null or empty when nothing is published — that domain is publishing its first record, not changing several fields — or when nothing differs.
Each entry in record_diff:
tag string required The DMARC tag name, lowercased — p, sp, rua, and so on.
kind string enum required One of: added, changed, removed
from string required The published value. Empty for an added tag.
to string required The recommended value. Empty for a removed tag.
note string required One sentence on what this tag does and what the change means.
ttl_advice object | null optional The step to take BEFORE publishing. Present alongside record_to_publish.
at_strongest boolean required Already at p=reject; there is no rung above.
needs_record boolean required No DMARC record is published at all
awaiting_first_report boolean required regressed boolean required The domain used to enforce a stronger policy than it does now — usually a DNS edit nobody meant to make.
regressed_from string enum optional One of: none, quarantine, reject
policy_history_unavailable boolean required A false regressed means "unknown" rather than "no" while this is true.
criteria array<object> required Each entry in criteria:
key string enum required no_unreviewed_sources was RETIRED and is no longer emitted. Its job is absorbed by acceptable_impact, where an unreviewed sender blocks by contributing impact rather than by existing.
One of: coverage_days, message_volume, distinct_providers, acceptable_impact, pass_rate, no_unaligned_sending, time_at_current_policy, no_new_unknown_sources
label string required met boolean required actual string required The value actually measured, populated whether or not the criterion is met — "97.2%" beside a required "98%" is a to-do list; a bare false is not.
required string required detail string optional blocking boolean required Whether failing this criterion withholds the rung. pass_rate is the only one where it is false: it explains the impact figure rather than gating on it, so a domain can be ready with pass_rate.met false. Never list a non-blocking criterion as a reason a domain is not ready.
pass_rate object required What the pass_rate criterion was actually measured over. Populated at p=reject too, where there is no criterion to judge but the rate is still real.
A distributed spoofing campaign sits in the denominator of the reported pass rate, so the domain most in need of enforcement shows the worst readiness and the ladder blocks on a number its owner cannot move. When a campaign is identified the rate is recomputed over the mail that could have authenticated, and campaign_excluded says so.
THIS IS NOT LENIENCY. The excluded mail is mail you never sent, from addresses that authenticated as nobody, and tightening the policy is exactly what stops it being delivered. There is nothing to confirm, reject or publish about it.
A second bucket comes out for a different reason: failing mail from sources you have explicitly REJECTED. You have said that mail is not yours, the impact preview already leaves it out on those grounds, and leaving it in the rate had two halves of one answer disagreeing about whose mail it is. It is reported separately from the campaign because "somebody is forging your domain" and "you told us this sender is not yours" are different facts, and only the second is a decision you can revisit.
A THIRD BUCKET COMES OUT, AND IT IS THE ONLY ONE THAT IS A COST RATHER THAN A REASON TO PUBLISH: your own mail, forwarded onward by a forwarder we can name and re-sent with a broken signature. Recipients choose to forward and you do not control that path, so it is not evidence of a fault you can fix and it does not decide the verdict. It stays in impact.would_affect_messages at full weight, because at p=quarantine that mail really is quarantined at receivers that do not override — and publishing past it requires a recorded acknowledgement.
Read the *_excluded flag beside each bucket before comparing adjusted against a pass rate from any other endpoint. A count may be non-zero while its flag is false — a bucket identified but not subtracted is named and not applied, and both rates are then the same number.
Fields of pass_rate:
raw number<double> required The pass rate over everything reported, in [0,1]. Every other endpoint reports this one.
adjusted number<double> required The rate the criterion was judged on, in [0,1]. Equal to raw when nothing was excluded.
messages integer<int64> required authenticable_messages integer<int64> required messages less the excluded campaign — the denominator adjusted was computed over. message_volume is judged on messages, not on this: a campaign never withdraws a rung the domain earned.
excluded_messages integer<int64> required excluded_sources integer<int64> required How many distinct addresses the excluded messages came from.
campaign_excluded boolean required Whether the campaign adjustment was applied. Not implied by excluded_messages being non-zero.
disowned_messages integer<int64> required Failing mail from sources you have rejected, with anything already counted in excluded_messages removed — the two overlap, and the campaign figure is a domain-level aggregate rather than a sum over source rows, so subtracting both whole would count the overlap twice.
disowned_sources integer<int64> required How many rejected sources contributed disowned_messages.
disowned_excluded boolean required Whether the disowned adjustment was applied. Not implied by disowned_messages being non-zero.
relay_broken_messages integer<int64> required Your own mail, broken on the way through a forwarder we can name. Out of this rate, IN impact.would_affect_messages at full weight: a cost of publishing, not a reason to publish.
relay_broken_sources integer<int64> required How many recognised forwarders contributed relay_broken_messages.
relay_broken_label string required The largest contributing forwarder by failures, so copy can name it. Empty when no name was available.
relay_broken_excluded boolean required Whether the relay adjustment was applied. Not implied by relay_broken_messages being non-zero.
top_failure_contributor object | null optional Null when no single source owns most of the domain's failures, which is the common case.
impact object | null optional What publishing target_policy would have done to the last thirty days of mail. Null for a domain already at p=reject, one awaiting its first report, and one with no record at all.
enforcement object | null optional What receivers actually DID with the published policy. Present only at p=quarantine or p=reject, and null when no failing mail was reported.
recommendation object | null optional What to do now: the record, the diff, the TTL step, the watch window, the way back, and copy specific to the moment. Null for a domain at the strongest rung and one awaiting its first report, where there is nothing to recommend.
PREFER IT OVER record_to_publish, which it supersedes. In particular, a domain that has just finished a watch window badly has no record_to_publish at all and a rollback_record that is the most useful string on the payload.
record_to_publish string optional Present only when ready. A record for YOU to publish — SendOps holds no DNS credentials and nothing on this API writes DNS.
A COMPATIBILITY FIELD, always equal to recommendation.record. A bare record is still the right answer to "what do I paste"; it is the wrong answer to every other question the recommendation answers.
recipient_impact string optional next_action object | null optional The single largest lever between this domain and target_policy, or null (SND-1398).
ONE, NEVER A LIST, and null is the common answer. criteria is already the list; what it cannot say is which of them costs the most and which row in sources to go and open. Quote this before the criteria when it is present, and do not manufacture one from the criteria when it is not — a weak next action is worse than none.
alignment_breadth object optional How much of this domain's PASSING mail passes on BOTH mechanisms rather than on one (SND-1383). A domain at 99% whose passes are almost all SPF-only is one forward away from failing: SPF is the leg forwarding destroys, and a recipient setting up a forward is not something the sender can prevent or even see. A domain at 99% on both mechanisms is solid. The pass rate is identical for both. NOT A CRITERION AND NEVER BLOCKING. A fragility warning, not a fault — the remedy is a DKIM key at a third party on their timetable, and a customer forwarded to by strangers cannot make forwarding stop. It is reported beside the verdict and is no part of it.
Fields of alignment_breadth:
measured boolean required False when the window carried no passing mail. Render nothing rather than "0% on both mechanisms", which reads as a finding about a domain nobody has heard from.
both_messages integer<int64> required Aligned on DKIM and SPF — the only mail that survives both a forward (which breaks SPF) and a reseller rewriting the body (which breaks DKIM).
dkim_only_messages integer<int64> required Aligned on DKIM alone. Normal for forwarded mail, and the durable half: a DKIM signature travels with the message.
spf_only_messages integer<int64> required Aligned on SPF alone — the fragile population. The moment anybody forwards it, the envelope sender changes and it fails.
passing_messages integer<int64> required The denominator: mail that passed on at least one mechanism. The three counts above are DISJOINT and sum to it. Failing mail is the pass rate's business and is not here.
both_share number<double> required dkim_only_share number<double> required spf_only_share number<double> required fragile boolean required True when enough of the pass rate rests on SPF alone to be worth saying so. It gates the sentence, never a verdict.
suite_signing_as_itself string required The mailbox suite signing with its own domain instead of the customer's, when a source carries that diagnosis; empty otherwise. The same fact as the per-source suite_signing_as_itself finding at domain altitude — and the half with a fix attached.
sentence string required What "aligns only on SPF" means, in the same words that use the phrase. Empty when there is nothing worth saying.
summary string required code is internal_error. The
request_id field can be quoted to SendOps support to investigate.
application/problem+json