The guardrails inside our DMARC generator
Marc's note on the guardrails inside our DMARC generator: report URI cleanup, external destination warnings, old syntax handling, and current DMARC record choices.
Standards basis: Advice based on RFC 9989 for DMARC policy records, RFC 9990 for aggregate reports, and RFC 9991 for failure reports. Historical RFC 7489 behavior is called out only where relevant.
The easiest DMARC generator to build is barely a generator. It is a form that joins a few strings:
v=DMARC1; p=none; rua=mailto:[email protected]
That was not the tool I wanted to ship.
Printing v=DMARC1 is not the hard part. The hard part is protecting people from records that look plausible while carrying the wrong decision: a missing mailto:, a report address on another domain, a strict alignment setting chosen because it sounds safer, or an old rollout tag copied from a 2018 blog post.
So our DMARC generator has guardrails. Some are visible. Some are just small pieces of Stimulus code that keep the record boring in the right places.
Report addresses are normalized, not trusted blindly
Most users type an email address into the aggregate report field. Some include mailto:. Some do not. Some paste a comma-separated list from an old DNS record.
The generator should not make the user babysit that formatting detail. It accepts the email address, adds mailto: when needed, validates the basic address shape, escapes commas inside the URI format, and then writes the generated rua or ruf value.
This sounds small until you have debugged a record that reads fine to a human but is not a valid DMARC report URI.
I like tools that remove syntax chores without hiding the protocol. The user should still understand that DMARC reports go to URIs, not magic inbox labels. They should not have to remember the prefix while they are trying to fix DNS.
External report destinations deserve a warning
If your domain is example.com and your rua address is [email protected], that is an external reporting destination.
That setup is normal for DMARC monitoring services, but it is more than a string change. External report destinations need DNS authorization from the receiving report domain. Without it, receivers can decline to send reports there.
The generator checks whether the report address domain sits outside the domain the user entered. If it does, the UI shows an external-domain warning and points to the _report._dmarc authorization requirement.
I like this warning because it appears at the point where a user might think, “I pasted the address, so I am done.” They are close, but DNS still has one more trust step.
Legacy report-size suffixes are accepted, then removed
Older DMARC examples sometimes include a size suffix on report URIs. Those values still get pasted into tools.
The generator accepts that input so the user is not punished for bringing an old record. Then it warns that the legacy suffix was removed from the generated output. Current DMARC report URI guidance keeps the report address simple.
That is the general rule I use for old syntax: be tolerant on input, conservative on output.
np is present because subdomain abuse is real
The non-existent subdomain policy, np, is one of the DMARCbis-era additions that is easy to overlook.
It controls the policy for mail claiming to come from subdomain names that do not exist in DNS. Think invoice-portal.example.com when that host was never created. Those names are useful to attackers because they look close enough to the brand while avoiding assumptions people make about the root domain.
The generator exposes np separately from sp because they answer different questions:
-
spis the policy for real subdomains unless they publish their own DMARC record. -
npis the policy for names that do not exist.
Most users can inherit the main policy. The option still belongs in the tool because the distinction is now part of current DMARC policy records, and it matters for domains with many unused subdomain shapes.
t=y replaced the way people used to think about pct
The old pct tag encouraged a tempting mental model: enforce on a slice of mail, then raise the number over time.
Current DMARC moved pct to historic status. New records should not use it as the normal rollout mechanism. The generator does not add it.
Instead, the tool offers testing mode, t=y. When present, it asks receivers to apply the next-lower policy while you test. A p=quarantine; t=y record lets you observe the move from monitoring toward enforcement, and a p=reject; t=y record lets you test with quarantine-like handling before removing the test flag.
This is one of those choices where a generator should not be neutral. If a tag is historic for new records, the tool should not keep offering it just because people search for it.
Relaxed alignment is the default for a reason
The generator defaults DKIM and SPF alignment to relaxed mode.
Strict sounds better. It often is not.
A lot of legitimate email uses subdomains. A third-party sender may sign with mail.example.com while the visible From domain is example.com. Under relaxed alignment, those domains share the same organizational domain and can align. Under strict alignment, they do not.
Strict alignment can be useful for domains with tightly controlled mail flows. For most organizations, it is a footgun until their reports prove every legitimate sender can survive it.
That is why the tool omits adkim=r and aspf=r from the generated record unless the user chooses strict. Relaxed is the default anyway. Keeping the record shorter is better than adding redundant tags to make the output look more sophisticated.
IDN handling happens before the record is trusted
People own domains outside plain ASCII too.
The generator converts domain input to punycode before validation. That lets the UI accept internationalized domain names while still checking the DNS-safe representation. It also means the “check this domain” flow can pass a usable domain to the rest of the site.
This is a quiet detail, but DNS tools are full of quiet details. If the input box accepts a domain shape that the rest of the application cannot validate, the tool has created a dead end.
The generator is not trying to be the tag guide
We already have a full DMARC record tags guide. The generator should not duplicate it.
The generator’s job is narrower: make the common path safe.
Choose a policy without accidentally starting too strict. Add aggregate reporting without malformed URIs. Avoid obsolete rollout habits. Keep relaxed alignment unless there is a tested reason not to. Warn when reports go to an external domain. Produce a short record that is easy to review.
After publishing, the next step is not another generator. It is validation. Run the domain through the DMARC checker, confirm what DNS actually serves, and then watch the reports before enforcement.
That is the difference between a string builder and a useful tool. A string builder asks, “Can I output a record?” A useful tool asks, “Will this record still make sense when a tired person pastes it into DNS?”
Marc