For a game SaaS, a welcome email deliverability checklist should cover more than inbox placement: verify the custom sending domain and DKIM, consult the suppression list, and preserve an audit trail that proves which account was notified and why another address was skipped. The useful question is not whether the mail API accepted a request. It is whether the team can explain the whole delivery path later.
Start with evidence.
Short answer: build the audit record first, then put a custom sending domain, DKIM, SPF, and suppression decision in front of the queue. Treat provider acceptance as one event in a longer delivery timeline. The checklist below is for a Node.js service, but the controls are provider-independent.
Start with the failures a dashboard hides
Deliverability work fails at boundaries. Before choosing a queue or adapter, test a valid new player, a previously suppressed address, a hard bounce, a duplicate notice, a provider timeout, and a delayed delivery event. For each case, assert the event sequence and the final deduplication state. An integration test that only checks for a resolved promise is not testing delivery reliability.
Separate transactional welcome mail from campaigns and support traffic where the transport allows it. Give the stream its own queue policy, template versioning, and alert dimensions. A campaign complaint should not make an account notice impossible to explain, and a template change should not erase the meaning of an older audit event.
Redact by default. A stable hash or an approved partial address is often enough for operations; retention rules may require a different choice. Store raw provider events separately when policy permits, and normalize only fields the event contract actually guarantees. I'm not sure every transport uses the same status vocabulary, so the normalized model should retain the original status instead of pretending the mapping is perfect.
For SMS, reuse the notice ID and audit shape but re-evaluate consent, sender identity, and opt-out rules. CTIA's messaging guidance is a useful starting point for the SMS/MMS compliance surface. Email authentication concepts do not transfer unchanged.
The build log starts with a notice record
The constraint that changes the design is replayability. A player may contact support about an account notice weeks after signup. A timestamp in an application log is weak evidence; it can be rotated, duplicated, or detached from the account that caused it. Give every notice a stable ID and record decisions as append-only events.
That ID should represent the business notice, not an HTTP request. A retry after a timeout is still the same welcome notice. Keep the account ID, template version, recipient in an approved redacted form, and sending-domain identity alongside it. Store the provider's attempt ID when one exists, but don't make that external ID your primary key.
The minimum event vocabulary is small: suppressed, submitted, delivery_observed, and delivery_failed. “Submitted” means the transport accepted the request. It does not mean an inbox accepted the message. This distinction is easy to lose in a dashboard with one green “sent” counter. For a concrete game example, imagine a player creates an account on a new device while the old address is already suppressed after a hard bounce. The new signup should create a new business notice only if policy permits it; it should not quietly reuse a browser-session retry, and it should not overwrite the earlier suppression event. A support engineer needs to see the account decision, recipient decision, authentication identity, transport attempt, and delayed provider event as separate facts, because collapsing them into “welcome email sent” makes a compliance review impossible.
I once treated that counter as the result. Bad assumption. A timeout can sit between your process and the mail service, so the application cannot know if a retry will duplicate the notice. The audit state should preserve “submission uncertain” until a later event or an operator decision resolves it.
The counter is not the record.
What should a SaaS Node.js welcome email deliverability checklist prove?
Use four gates, in this order:
- The notice is authorized for this account and environment.
- The recipient is not on the current suppression list.
- The selected custom sending domain is authenticated and matches the release configuration.
- The transport attempt and later delivery events can be joined to the notice ID.
This order matters. DNS is not a substitute for consent or suppression policy, and a suppression lookup is not an audit trail by itself. Each gate needs a recorded result, including a negative result.
Here is the smallest useful application boundary. It intentionally has no vendor-specific route or SDK. The adapter can sit over SMTP or an HTTP service; the policy code should not care.
type SuppressionReason = "unsubscribe" | "hard_bounce" | "abuse" | "policy";
type NoticeState = "suppressed" | "submitted" | "delivery_observed" | "delivery_failed";
type WelcomeNotice = {
noticeId: string;
accountId: string;
recipient: string;
templateVersion: string;
};
type NoticeEvent = {
noticeId: string;
state: NoticeState;
recordedAt: string;
reason?: SuppressionReason;
attemptId?: string;
};
type MailAdapter = {
submit(input: {
from: string;
to: string;
subject: string;
text: string;
headers: Record<string, string>;
}): Promise<{ attemptId: string }>;
};
async function submitWelcome(
notice: WelcomeNotice,
mail: MailAdapter,
findSuppression: (address: string) => Promise<SuppressionReason | null>,
appendEvent: (event: NoticeEvent) => Promise<void>,
): Promise<void> {
const reason = await findSuppression(notice.recipient);
if (reason) {
await appendEvent({
noticeId: notice.noticeId,
state: "suppressed",
reason,
recordedAt: new Date().toISOString(),
});
return;
}
const result = await mail.submit({
from: "accounts@updates.example-game.com",
to: notice.recipient,
subject: "Your game account is ready",
text: "Your account is ready.",
headers: {
"X-Notice-Id": notice.noticeId,
"X-Template-Version": notice.templateVersion,
},
});
await appendEvent({
noticeId: notice.noticeId,
state: "submitted",
attemptId: result.attemptId,
recordedAt: new Date().toISOString(),
});
}
The code does not catch every exception because the durable event store and retry policy are application decisions. In production, make the event write transactional with the deduplication record, or use an outbox. The important invariant is that a successful submission cannot disappear between the mail adapter and the audit log.
Make domain authentication a release check, not a setup ritual
A custom sending domain gives a game team a clear boundary for transactional traffic. It does not confer trust automatically. SPF describes which hosts are authorized to use a domain in the SMTP envelope; RFC 7208 is the relevant specification. Publish the authorization your architecture actually uses and check the resulting DNS record from outside the build network.
DKIM is a second release gate. Verify that the selector's public key is available and that the signing domain is intentional. Selector rotation needs overlap: publish the new key before messages signed with it can leave the system, then retire the old key only after its traffic window has passed. The exact window is an operational choice, so measure it from observed traffic rather than guessing.
The preflight should compare configuration, not just query DNS. Check the visible From domain, envelope domain, selector, environment, and template stream. A staging domain accidentally reused in production is a configuration failure even when the DKIM signature is technically valid.
Keep the check boring. Boring checks ship.
What would I change at scale, and when is this design unsuitable?
At small volume, one queue, one transactional stream, and a durable outbox are enough. As regions, games, and sending identities multiply, add a configuration registry with reviewed ownership, a delivery-event consumer, and alerts on missing events rather than only on transport errors. Measure time from signup to submission, submission to observed delivery, suppression rate, duplicate-attempt rate, and the age of unresolved submissions.
The trade-off is operational weight. An outbox, event consumer, DNS preflight, and retention policy create more moving parts than a direct send call. This design is not suitable when the message has no audit requirement and occasional duplicate or missing notifications are acceptable. Stick with a simpler mail path for that case, but keep suppression and authentication checks explicit.
It is also a poor fit if the team cannot own domain DNS, event retention, or an on-call response for ambiguous submissions. A hosted transport can reduce glue code, while a self-managed or multi-transport setup can offer more routing control. Neither option removes the need to define what “delivered” means for the game.
The decision rule is straightforward: choose the implementation that can answer “why was this welcome notice sent, withheld, or left uncertain?” with one stable notice ID and a readable event trail. That is a better reliability test than any vendor feature matrix.
Further reading
- RFC 7208, Sender Policy Framework (SPF): https://datatracker.ietf.org/doc/html/rfc7208
- CTIA messaging interoperability and compliance best practices: https://www.ctia.org/the-wireless-industry/industry-commitments/messaging-interoperability-sms-mms
Top comments (0)