Fintech Mail Readiness with Application Logs and Live DNS Reads (Audit Checkpoints)
Short answer: for a fintech SaaS letting customers use their own domain, preserve application change events and independently sample DNS. Neither record is a complete substitute for the other. The first says what your sy
Short answer: for a fintech SaaS letting customers use their own domain, preserve application change events and independently sample DNS. Neither record is a complete substitute for the other. The first says what your system requested and when; the second says what a resolver could observe at a particular time. For an audit, join the two by domain, record type, expected value, and observation time, then retain mismatches rather than flattening them into a single "verified" flag.
| Evidence source | Can establish | Cannot establish |
|---|---|---|
| Application event log | Who requested a domain change, intended records, and the sequence your service accepted | That a customer published the records or that resolvers could see them |
| DNS observation | The answer returned for a specific name, type, resolver, and time | Who changed the zone, what was there between samples, or what every resolver saw |
Recommendation: use the event log as the intent timeline and time-stamped DNS observations as external evidence. Require both for a claim that a customer domain was ready at a given checkpoint. This is a deliverability decision, not a contest to pick the cheaper data store. A one-person team shipping weekly needs evidence that can answer a support or compliance question without reconstructing yesterday's DNS from today's answer.
Can application logs and live DNS zone reads establish complete history?
A lookup is a point-in-time observation through a particular resolver. DNS caching matters: RFC 1035 describes TTL as the interval a resource record may be cached, while RFC 2308 covers negative caching of absent answers. An answer of "not found" after an application accepted a new TXT record request can reflect caching; it does not prove that the customer never published the record. Conversely, a successful lookup today cannot prove that the same value was present last Tuesday.
For a custom domain used in email, this gap has operational consequences. DMARC policy is published in DNS, and RFC 7489 describes aggregate reports as feedback about authentication results. A DMARC TXT lookup is evidence of a published policy at the time of observation. It is not proof that every message was delivered.
Keep authentication and delivery claims separate.
Sample the exact owner name and record type relevant to the decision. Store the queried name, type, returned RRset or absence, observation timestamp, resolver identity, and observed TTL. A resolver response is useful evidence, but it is a limited vantage point. If the consequence of a false "ready" state is high, corroborate from another resolver or repeat after the relevant cache interval. Never turn an isolated timeout into "record absent."
Which history belongs in the application?
The app knows which customer initiated verification, which domain they asserted control over, what value it asked them to publish, and when the verification state changed. Record these as append-only events with timestamps and stable domain identifiers. Do not keep only the current settings row: overwriting an expected TXT token destroys the earlier expectation that a later observation must be compared against.
There is a real trade-off here. Logging every UI click produces noise, while logging only successful verification omits failed attempts and reversals. I would retain accepted configuration changes, verification attempts and outcomes, and removal events; a read-only page view has no bearing on whether a domain was ready. Restrict access to tokens and customer identifiers, and set retention according to the applicable compliance requirement rather than treating an audit log as an unlimited data dump.
Application events still have a blind spot.
A customer can edit their DNS zone without telling your app. A scheduled observation can catch drift, but no finite polling interval reconstructs every intermediate state. If the audit question is "who edited the customer's zone," the customer's authoritative DNS provider must supply that history. Your application cannot manufacture it. This distinction sets the limit on an audit claim: an application event at 09:00 and a resolver observation at 09:10 establish two separate facts, not a continuous account of those ten minutes. Even two successful observations on either side of an interval cannot rule out a short-lived change between them. Keep both timestamps visible to the reviewer rather than replacing them with a single green status.
A small reconciliation record
The example below models evidence, not a DNS client. Its caller supplies normalized observations from a resolver and immutable application events. Comparing the full expected RRset, rather than just asking whether one string appears, matters when a customer has several TXT values at the same owner name. Match values according to the record-specific rule you actually enforce; the simplified equality here is only suitable for an already normalized, single expected value.
type Change = {
domainId: string;
name: string;
type: "TXT";
expected: string;
acceptedAt: string;
};
type Observation = {
domainId: string;
name: string;
type: "TXT";
value: string | null;
observedAt: string;
resolver: string;
outcome: "answer" | "absent" | "timeout";
};
function reconcile(change: Change, sample: Observation) {
if (change.domainId !== sample.domainId ||
change.name !== sample.name || change.type !== sample.type) {
throw new Error("Observation does not match the requested record");
}
if (Date.parse(sample.observedAt) < Date.parse(change.acceptedAt)) {
throw new Error("Observation predates the change");
}
return {
change,
sample,
status: sample.outcome === "timeout" ? "unknown"
: sample.outcome === "answer" && sample.value === change.expected
? "observed" : "mismatch",
};
}
In production, store the original lookup result beside the normalized comparison and version the comparison rule. A changed parser must not silently rewrite an old decision. Put a retryable timeout in an unknown state, record mismatches with their timestamps, and alert on persistent drift rather than on every transient miss. A weekly release cadence is easier to sustain when support can inspect one event and its attached observations instead of running fresh queries and guessing what happened before deployment.
When is the other source enough?
If the question is strictly "what did our service ask this customer to configure?", the application log is sufficient. If the question is "what does this resolver return now?", a fresh lookup is the right tool. Neither narrow answer requires pretending to have historical zone snapshots. The combined approach earns its cost when a domain-ready decision must be defended after the customer edits DNS, caches expire, or authentication reports arrive later.
For a solo SaaS, I would outsource commodity DNS querying or storage operations where appropriate, but keep the evidence schema and readiness rule under application control.
Spend engineering hours on the boundary where an incorrect domain-ready claim affects customer mail; do not spend them building an imaginary complete history from occasional lookups.
References
- https://www.rfc-editor.org/rfc/rfc1035
- https://www.rfc-editor.org/rfc/rfc2308
- https://datatracker.ietf.org/doc/html/rfc7489
Sources
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.