Did this change work? The same figures either side of it
Recomputes the domain's figures over a window before and a window after one timeline moment: pass rate raw and adjusted with all three exclusion buckets, volume, the spoofing campaign, what publishing would have affected, what receivers did, and source counts by class and review state.
This is the only place that can answer "did the change work". Every other figure in the product is a rolling thirty days that mixes both sides of a change together.
Read delta_available before subtracting anything. It is false, and
no_delta_reason says why in a sentence, in three cases — each of which
is a refusal rather than a gap:
- a side reported fewer than
min_messages_per_sidemessages, so any difference is noise. "Too early to tell" and "too quiet" are different answers and the reason says which (after_incompletedistinguishes them); - another change happened inside one of the windows, so nothing here can
be attributed to this one.
attribution_declinedis set andneighboursnames the other changes. The figures are still real — what is withheld is the claim about cause; - both at once.
guard_band_days are excluded from the "after" side. Aggregate
reports arrive 24–48 hours after the mail they describe, so the days
immediately following a change carry mail sent and judged under the OLD
policy. Including them would attribute the tail of the old policy to the
new one.
truncations names any window report history could not fill, and by how
many days. That is different from after_incomplete, which means the
window has not finished yet — nothing was lost, the evidence has not
arrived.
annotationId comes from a timeline event's sightings. Where both
detectors saw the change, anchor_annotation_id names the one the
windows were actually split on — the dns sighting wins, because its
timestamp is a poll rather than a midnight report bucket.
The classification and the customer's decisions are as of now on both sides. A source reclassified last week would otherwise move between the columns and read as mail that changed hands, when what changed was our opinion of it.
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.
annotationId string<uuid> required An annotation id from a timeline event's sightings.
Responses
Errors follow the RFC 7807 problem format — see the error reference.
annotation_id string<uuid> required What was asked about.
anchor_annotation_id string<uuid> required What the windows were actually split on. Differs from annotation_id whenever both detectors saw the change: the dns sighting wins, because its timestamp comes from a poll rather than a day bucket, and splitting on the coarser date would put up to a day of post-change mail into the "before" side.
anchor_origin string enum required One of: dns, reports, user, system
occurred_at string<date-time> required The anchor's — the instant the two windows meet.
kind string required old_policy string required new_policy string required old_record string optional new_record string optional before object required One window's figures.
Fields of before:
from string<date-time> required to string<date-time> required days integer required The window's length. Shorter than window_days means it was truncated — read truncations.
measured boolean required Whether this side cleared min_messages_per_side. False means no delta is offered.
volume integer<int64> required pass_volume integer<int64> required dkim_volume integer<int64> required spf_volume integer<int64> required 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.
dispersion object | null optional The spoofing campaign measured OVER THIS WINDOW, not the stored thirty-day figure (which spans both sides of any change and so can be attributed to neither). Null means the read failed — NOT that there was no campaign.
would_affect_messages integer<int64> required What publishing a tighter policy would have stopped in this window: the failing volume of every contributing source, less the ones the customer has disowned.
enforcement object | null optional What receivers did with the policy published DURING this window — the old policy before the change, the new one after. Null below quarantine, where every disposition is legitimately none and the figure would be a tautology.
sources_by_class object required Registry rows that sent in this window, by effective class. Every class is present even at zero, so the two sides can be subtracted straight across — a missing key and a zero are the same fact and only one of them is legible.
sources_by_review_state object required after object required One window's figures.
Fields of after:
from string<date-time> required to string<date-time> required days integer required The window's length. Shorter than window_days means it was truncated — read truncations.
measured boolean required Whether this side cleared min_messages_per_side. False means no delta is offered.
volume integer<int64> required pass_volume integer<int64> required dkim_volume integer<int64> required spf_volume integer<int64> required 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.
dispersion object | null optional The spoofing campaign measured OVER THIS WINDOW, not the stored thirty-day figure (which spans both sides of any change and so can be attributed to neither). Null means the read failed — NOT that there was no campaign.
would_affect_messages integer<int64> required What publishing a tighter policy would have stopped in this window: the failing volume of every contributing source, less the ones the customer has disowned.
enforcement object | null optional What receivers did with the policy published DURING this window — the old policy before the change, the new one after. Null below quarantine, where every disposition is legitimately none and the figure would be a tautology.
sources_by_class object required Registry rows that sent in this window, by effective class. Every class is present even at zero, so the two sides can be subtracted straight across — a missing key and a zero are the same fact and only one of them is legible.
sources_by_review_state object required window_days integer required How long each side was meant to be.
guard_band_days integer required Days after the change excluded from the "after" side, because reports lag 24–48 hours and those days carry mail sent and judged under the OLD policy. Always stated rather than left to be inferred from the dates.
min_messages_per_side integer required delta_available boolean required Whether the two sides may be subtracted. The field to branch on — a consumer that subtracts regardless will be wrong on a real domain.
no_delta_reason string optional Why not, in a sentence a customer can act on.
attribution_declined boolean required Another change fell inside one of the windows. The figures are still real; what is withheld is the claim that this change caused them.
neighbours array<object> required Each entry in neighbours:
annotation_id string<uuid> required kind string required origin string required occurred_at string<date-time> required old_policy string optional new_policy string optional side string enum required One of: before, after
truncations array<object> required Each entry in truncations:
side string enum required One of: before, after
requested_from string<date-time> required from string<date-time> required lost_days integer required reason string required Prose a customer can act on.
after_incomplete boolean required The "after" window has not finished yet. DISTINCT FROM A TRUNCATION: nothing was lost, the evidence has not arrived, and the difference is the whole of what a reader does next.
code is internal_error. The
request_id field can be quoted to SendOps support to investigate.
application/problem+json