DMARC monitoring
Put a domain under DMARC monitoring from your own code: enroll it, publish the DNS record we hand back, and check when reports start flowing.
POST /v1/dmarc/domainsGET /v1/dmarc/domainsGET /v1/dmarc/domains/{id}PATCH /v1/dmarc/domains/{id}DELETE /v1/dmarc/domains/{id}GET /v1/dmarc/domains/{id}/dnsPOST /v1/dmarc/domains/{id}/verifyEvery endpoint takes a secret key in the Authorization header — see
Authentication.
The flow
Section titled “The flow”Three steps, and the first one gives you everything you need for the second:
- Enroll the domain with
POST /v1/dmarc/domains. The response includes the exact TXT records to publish. - Publish those records in the domain’s DNS.
- Verify with
POST /v1/dmarc/domains/{id}/verifyto confirm we can see them. Reports begin arriving at the next reporting interval — mailbox providers send aggregate reports roughly daily, so allow up to 24–48 hours before the first one lands.
Enroll a domain
Section titled “Enroll a domain”POST /v1/dmarc/domains| Field | Type | Required | Description |
|---|---|---|---|
domain |
string | Yes | The domain to monitor, e.g. example.com. |
org_id |
string | No | Your own grouping key, echoed back on reads. |
is_subdomain |
boolean | No | Mark this as a subdomain enrollment. |
parent_domain |
string | No | The parent, when is_subdomain is true. |
ruf_enabled |
boolean | No | Also provision a forensic (RUF) address. |
tlsrpt_enabled |
boolean | No | Also provision a TLS-RPT address. |
retention_days |
integer | No | How long to keep reports (1–3650). |
curl -X POST https://api.tryverifyemail.com/v1/dmarc/domains \ -H "Authorization: Bearer sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{"domain": "example.com"}'const res = await fetch("https://api.tryverifyemail.com/v1/dmarc/domains", { method: "POST", headers: { Authorization: `Bearer ${process.env.VERIFYEMAIL_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ domain: "example.com" }),});
const { domain, dns_records } = await res.json();import os, requests
res = requests.post( "https://api.tryverifyemail.com/v1/dmarc/domains", headers={"Authorization": f"Bearer {os.environ['VERIFYEMAIL_API_KEY']}"}, json={"domain": "example.com"},)
payload = res.json()Response
Section titled “Response”201 Created on first enrollment.
{ "created": true, "domain": { "id": "665f1c2a9b4e7d0012ab34cd", "domain": "example.com", "status": "pending", "is_subdomain": false, "parent_domain": null, "org_id": null, "ruf_enabled": false, "tlsrpt_enabled": false, "retention_days": 90, "published_policy": null, "reporting": { "rua": "a1b2c3d4e5f6...@dmarc.tryverifyemail.com", "ruf": null, "tlsrpt": null }, "created_at": "2026-08-19T10:04:11.000Z", "updated_at": "2026-08-19T10:04:11.000Z" }, "dns_records": [ { "record": "dmarc", "host": "_dmarc.example.com", "type": "TXT", "action": "create", "value": "v=DMARC1; p=none; rua=mailto:a1b2c3d4e5f6...@dmarc.tryverifyemail.com", "current": null } ]}The DNS records
Section titled “The DNS records”dns_records is generate-or-merge, not a blind template. We read whatever is
currently published at _dmarc.<domain> first:
"action": "create"— nothing is published yet.valueis a safe monitoring-only starter (p=none), so enrolling cannot affect delivery."action": "update"— a DMARC record already exists.valueis that record with our reporting address spliced into itsrua=list and every one of your existing policy tags preserved (p,pct,sp,adkim,aspf).currentshows what is published today so you can diff before applying.
Publish value verbatim at host.
Check verification
Section titled “Check verification”POST /v1/dmarc/domains/{id}/verifyRe-reads the domain’s live DNS and moves its status.
curl -X POST https://api.tryverifyemail.com/v1/dmarc/domains/{id}/verify \ -H "Authorization: Bearer sk_your_api_key"{ "domain": "example.com", "state": "waiting", "status": "verified", "token_found": true, "published_policy": { "p": "none", "rua": "mailto:a1b2c3d4e5f6...@dmarc.tryverifyemail.com", "raw_record": "v=DMARC1; p=none; rua=mailto:a1b2c3d4e5f6...@dmarc.tryverifyemail.com" }, "last_report_received_at": null}state |
Meaning |
|---|---|
pending |
Our reporting address is not in your published _dmarc record yet. |
waiting |
DNS is correct. No reports have arrived yet — this is normal for the first 24–48 hours. |
receiving |
Reports are flowing. Monitoring is live. |
A "dns_error": true field means the DNS lookup itself failed transiently. The
domain’s status is deliberately left unchanged in that case — a lookup that
did not answer is not evidence that your record is gone. Retry.
Preview the DNS records again
Section titled “Preview the DNS records again”GET /v1/dmarc/domains/{id}/dnsReturns the same records array as enrollment, re-read against current DNS. Use
it to re-check what still needs publishing without creating anything.
List monitored domains
Section titled “List monitored domains”GET /v1/dmarc/domains{ "domains": [ { "id": "665f...", "domain": "example.com", "status": "receiving" } ] }Retrieve one domain
Section titled “Retrieve one domain”GET /v1/dmarc/domains/{id}Returns a single domain object in the shape shown under
enroll. Unknown or not yours → 404.
Update a domain
Section titled “Update a domain”PATCH /v1/dmarc/domains/{id}Accepts org_id, is_subdomain, parent_domain, retention_days,
ruf_enabled and tlsrpt_enabled. At least one field is required.
Setting ruf_enabled or tlsrpt_enabled to true provisions that reporting
address if it does not exist yet — call GET /{id}/dns afterwards to pick up
the record you now need to publish.
Delete a domain
Section titled “Delete a domain”DELETE /v1/dmarc/domains/{id}{ "deleted": true }Removes the enrollment and revokes its reporting addresses; reports for the
domain are purged. Deleting a domain you do not have enrolled answers
{"deleted": false} rather than a 404 — the end state you asked for already
holds.