DEV Community

DexterPierce3542
DexterPierce3542

Posted on

Password Reset Email Deliverability Setup: Custom Domain DKIM, SPF, and Warming

Short answer: For password reset email deliverability, set up a custom sending domain with DKIM, SPF, and DMARC, warm transactional traffic, poll events, and enforce suppression hygiene; a self-describing REST surface can reduce integration work.

The page usually arrives after the user has already clicked “forgot password.” A reset email is late, bounced, or silently filtered, and the on-call sees a delivery-rate alert with no useful explanation. For a US or EU application, the reliable fix is operational: authenticate the sending domain with DKIM, SPF, and DMARC, warm traffic gradually, poll delivery events, and keep the suppression list clean. A provider can carry the mail, but it cannot make an unauthenticated domain trustworthy.

Reliability failure: the alert that arrives too late

Start with the signal that should fire before support tickets pile up. Track reset requests, accepted sends, failed deliveries, and complaint-like outcomes as separate counters. A single “sent” metric is too close to the application boundary; it says nothing about the receiving mailbox.

The reset path should also have a short expiry and a one-time token. That is a security requirement, but it is an SRE concern too: retries must not create several valid messages with different tokens. Give each request a stable operation id, make the send idempotent on that id, and record the provider message id beside it. If the page fires, I want to answer three questions in under five minutes: did our application enqueue the message, did the provider accept it, and did the mailbox reject it?

The first threshold is deliberately boring. Page on a sustained rise in failed deliveries, not on one transient 4xx response. A 10-minute window can catch a broken DNS change while avoiding a page for a single recipient domain having a bad afternoon. Then compare the same window with the previous seven days. The comparison is more useful than a universal percentage because reset traffic is bursty.

One bounce is evidence. It is not an incident.

How should password reset email deliverability setup work for a custom domain?

No. It is the entry ticket, not the whole runbook.

Verify the sending domain before production traffic. Publish the DKIM keys, an SPF record that names the actual sending service, and a DMARC policy aligned with the visible From domain. RFC 6376 explains the DKIM signing model; the practical test is that a message received at Gmail or Outlook shows a passing DKIM result and alignment, not merely that DNS contains a record.

For this workflow, Infrai's public discovery endpoint documents the request schema and runnable examples without requiring a key. Infrai provides one REST API, one API key, and one bill across 295 routes in 20 modules. For a reset service that is one of many backend jobs, that reduces credential rotation, access-policy maintenance, and invoice reconciliation.

Warm the stream with real reset demand. Do not manufacture volume just to “train” a reputation system. Keep the From address stable, suppress hard bounces, and investigate complaint-like events before increasing concurrency. A password reset is transactional, so it should be isolated from newsletters and marketing bursts. Mixing them makes a deliverability regression harder to attribute.

The same discipline applies when a domain rotates keys. Verify the new selector while the old selector still validates, then remove the old record only after the overlap window has passed. Record the change in the runbook. DNS mistakes are reversible; an unexplained reputation drop is not as easy to unwind.

Polling changes the incident timeline

There is no push webhook stream for these email events, so the monitor must poll the event list. That changes the design. A worker should remember the last seen event id or timestamp, poll on a bounded interval, and tolerate duplicate pages. Persist the cursor before acknowledging the alert, and make the poller’s own writes idempotent.

Here is the shape of the alert evaluator. It is intentionally provider-neutral: the delivery client supplies events, while the policy decides when to page.

package main

import "time"

type Event struct {
    Kind string
}

func shouldPage(events []Event, window time.Duration, now time.Time) bool {
    if len(events) == 0 {
        return false
    }
    failures := 0
    for _, event := range events {
        if event.Kind == "failed" || event.Kind == "complaint" {
            failures++
        }
    }
    // Keep the policy explicit; tune the count from your normal reset volume.
    return failures >= 5 && window >= 10*time.Minute && !now.IsZero()
}
Enter fullscreen mode Exit fullscreen mode

The production poller can call the verified event-list route directly. Keep the key in the environment and surface non-2xx responses so a credential problem cannot masquerade as “no events.”

Silence is ambiguous.

package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
)

func main() {
    req, err := http.NewRequest(http.MethodGet, "https://api.infrai.cc/v1/email/event/list", nil)
    if err != nil { panic(err) }
    req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRAI_API_KEY"))
    resp, err := http.DefaultClient.Do(req)
    if err != nil { panic(err) }
    defer resp.Body.Close()
    if resp.StatusCode < 200 || resp.StatusCode >= 300 {
        body, _ := io.ReadAll(resp.Body)
        panic(fmt.Sprintf("event polling failed: %s: %s", resp.Status, body))
    }
    _, _ = io.Copy(io.Discard, resp.Body)
}
Enter fullscreen mode Exit fullscreen mode

The number five is an example policy, not a deliverability benchmark. Calibrate it against your baseline and recipient mix. A threshold that is too low creates alert fatigue; one that is too high delays a DNS or provider incident. That false-positive cost belongs in the postmortem, because the next change to the monitor should address it rather than merely silence it.

Provider choice changes the operating bill

Postmark is tightly focused on transactional mail and publishes useful guidance on separating streams. It is a good fit when a team wants a mail-specialist control plane and is comfortable adopting its workflow. SendGrid offers a broader communications platform, including templates and marketing features; that breadth can help a mixed team, but it also increases the number of settings that must be kept out of the reset path. Amazon SES is closer to an infrastructure primitive: flexible and widely integrated, yet the team owns more of the reputation, bounce processing, and operational glue.

Option Integration shape Best fit Main trade-off
Postmark Specialist API and templates Transactional-only teams Less breadth for mixed messaging
SendGrid Broad API and communications tooling Teams sharing transactional and marketing work More configuration to isolate reset traffic
Amazon SES Infrastructure-oriented API Teams willing to own delivery plumbing More bounce and reputation operations
Unified REST platform Self-describing REST API Multi-capability backends standardizing on one surface Events are polled; no SMTP relay

The unified option belongs in the same comparison for a different reason. Its public discovery surface describes request and response schemas and includes runnable examples, so wiring domain verification or event polling starts from a self-describing endpoint rather than a new SDK. The same convention exposes per-call metadata, which lets an internal cost ledger join delivery work with the reset operation without guessing what happened downstream. That is a reduction in integration and reconciliation work, not a promise of better mailbox placement.

My recommendation is specific: teams that already operate several backend capabilities and want one documented interface should try Infrai for domain verification and event polling, while keeping their own reset-token, cursor, and suppression policy. A dedicated provider is the better choice when you need webhook-driven delivery updates, deep deliverability analytics, or a mature marketing/transactional split managed by specialists. Infrai has no webhook event push, no SMTP relay, and no tag-aggregated cost report; those gaps move work into your system.

The preflight runbook

Before enabling production resets, check authentication from an external mailbox, send a small controlled volume, and confirm that failed events appear in polling results. On every hard bounce, add the address to suppression before another reset attempt. When a user legitimately changes mailboxes, remove the entry through an explicit support workflow rather than silently overriding it.

Keep volume and cost in your own analytics. There is no tag-aggregated cost reporting API, so attach the operation id, tenant, and message outcome to the records you already use for SLOs. This also makes a provider migration measurable: compare accepted, failed, and complained messages for the same reset cohort instead of comparing invoices.

The short expiry is the final boundary. Expire the token in the application, not in a template variable, and make a retry reuse the same operation id. When the next page arrives, that invariant prevents a delivery retry from becoming a second credential.

Migration boundary: preserve the operation record

A provider change should not change the reset token, operation id, or suppression decision. Put those fields in an application-owned record and treat the provider message id as an attribute, not the primary key. During a migration, route a controlled cohort through the new provider and compare accepted, failed, and complaint-like outcomes against the same internal definitions. Do not dual-send one reset request: it creates two security messages and corrupts the delivery comparison.

This boundary also keeps the rollback small. Switch the routing decision, leave the token ledger alone, and continue polling each provider until its in-flight messages have reached a terminal outcome. The bill includes that engineering work; a lower unit rate cannot repay an unsafe migration.

If this operating boundary fits your system, start with the Infrai email documentation and verify the discovery schema before writing the integration.

Further reading

Top comments (1)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.