Concepts › Profiles and versions
Profiles and versions
A profile is what a tenant expects of a domain's records: a named list of requirements, each with a key you choose. Registering a domain does not point at "the sending profile" in the abstract. It pins the specific version that existed at that moment.
curl -s -X POST https://api.propgate.dev/v1/profiles \
-H "authorization: Bearer pg_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H 'content-type: application/json' -d '{
"key": "sending",
"requirements": [
{ "key": "spf", "check": "spf", "include": "_spf.google.com" },
{ "key": "dkim", "check": "dkim", "selector": "google" },
{ "key": "dmarc", "check": "dmarc" }
]
}'{
"data": {
"object": "profile",
"id": "019fbf10-...",
"key": "sending",
"version": 1,
"requirements": [
{
"key": "spf",
"check": "spf",
"include": "_spf.google.com"
},
{
"key": "dkim",
"check": "dkim",
"selector": "google"
},
{
"key": "dmarc",
"check": "dmarc"
}
]
},
"error": null,
"meta": null
}The CLI has no equivalent yet — a profile is created over the API only.
Editing writes a new version
Posting the same key again does not change version 1. It writes version 2
and leaves the first row exactly as it was:
curl -s -X POST https://api.propgate.dev/v1/profiles \
-H "authorization: Bearer pg_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H 'content-type: application/json' -d '{
"key": "sending",
"requirements": [
{ "key": "spf", "check": "spf", "include": "_spf.google.com" },
{ "key": "dkim", "check": "dkim", "selector": "google" },
{ "key": "dmarc", "check": "dmarc" },
{ "key": "mail", "check": "mx", "expectsMail": true }
]
}'{
"data": {
"object": "profile",
"id": "019fbf22-...",
"key": "sending",
"version": 2,
"requirements": [
{
"key": "spf",
"check": "spf",
"include": "_spf.google.com"
},
{
"key": "dkim",
"check": "dkim",
"selector": "google"
},
{
"key": "dmarc",
"check": "dmarc"
},
{
"key": "mail",
"check": "mx",
"expectsMail": true
}
]
},
"error": null,
"meta": null
}Every domain already registered against sending stays pinned to whichever
version it was registered against. Version 2 changes what a new
registration means, not what an existing domain is judged by.
This is not a caution about a hypothetical. Webhooks fire on every state transition, so an edit that reclassified every domain on a profile at once would land as a storm in every one of your customers' inboxes, with no deploy on your side to explain it. Pinning the version is what keeps "we tightened our sending profile" from turning into "we tightened it and every customer got paged."
Rejected at write time
A definition is refused before it is ever stored if a requirement inside it could never be answered: a duplicate key, two requirements competing for one check, a DKIM requirement with no selector, a CAA requirement with no issuer.
curl -s -X POST https://api.propgate.dev/v1/profiles \
-H "authorization: Bearer pg_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H 'content-type: application/json' -d '{
"key": "sending",
"requirements": [
{ "key": "spf", "check": "spf" },
{ "key": "spf", "check": "dmarc" }
]
}'{
"data": null,
"error": {
"message": "duplicate requirement key \"spf\""
},
"meta": null
}Accepting a definition like this and failing later, at read time once a
customer is already relying on it, would be a promise the API took and could
not keep. A 422 at write time is the same finding delivered while it is
still cheap to fix.
Check kinds
Six exist, rendered here straight from the same table the API reference and the tests read, so this list cannot say something the resolver cannot actually check.
caaThe CAA tree authorises a named certificate authority. Rejected without an issuer: the evaluator has nothing to compare against, so the requirement could never be reported on.
caaIssuer— Required. The CA that must be authorised, e.g. letsencrypt.org.
delegationEvery nameserver in the delegation answers authoritatively and agrees. Catches lame delegations and stale NS records, which look like intermittent outages to everyone else.
dkimrepeatableA selector publishes a valid, usable key. The one requirement type that may appear more than once, because DKIM answers a question per selector rather than per domain.
selector— Required. The label before _domainkey.expectedPublicKey— Optional. The key you issued. Supplying it turns “a valid key is published” into “your key is published”, which is what catches a domain that pasted someone else's record.
dmarcA valid DMARC record is discoverable at the right name. A p=none policy is a warning, not a failure — there is deliberately no way to require a minimum policy, because the evaluator cannot assert one and a requirement nobody can evaluate is a promise this API would not keep.
mxMail is deliverable, or correctly declared undeliverable. Whether a null MX is right depends entirely on intent, which no amount of looking at DNS reveals.
expectsMail— Optional, and tri-state. Omit it if you do not know. false asserts the domain receives no mail, which makes a null MX correct rather than a fault.
spfThe SPF record authorises your sending infrastructure and is within the RFC limits.
include— Optional. The include: token you publish. Expanded recursively, the way an MTA would, with the RFC 7208 ten-lookup and two-void-lookup limits enforced.ip— Optional. A specific sending address to evaluate the record against.
Next
Verdicts and state covers what a check against one of these requirements can come back as, and how that maps onto a domain's state.