DEV Community

VespasianBlack3884
VespasianBlack3884

Posted on

Why I Chose Password Reset Email Deliverability Setup for Support SaaS (and Why)

When a support SaaS routes a contact form or password reset, I choose an evidence-first email path: authenticate the sending domain, record each decision, and make suppression state part of the application data model. Delivery is useful only when we can show why a message was allowed, deferred, or refused.

Implement the audit ledger before the email exists

The system has two related flows. A customer submits a contact form and gets routed to a support queue; a user can also request a password reset. Both flows use the same outbound mail boundary, but they do not have the same risk. A reset token is security material, while a queue notification is operational evidence.

My invariants are deliberately boring:

  • The reset request never reveals whether an address exists. OWASP recommends a consistent response and timing, single-use tokens, and an expiration window.
  • The envelope sender, DKIM signing domain, and visible From domain are documented and verified before production traffic.
  • A hard bounce creates a durable suppression record. The next send checks that record before it calls the provider.
  • Every decision has an ID, timestamp, template version, domain status, and outcome. A compliance reviewer should not need to grep application logs.

This is the boundary that tends to get missed: DNS verification proves control of a domain; it does not prove that a recipient will accept a message. SPF authorizes infrastructure, DKIM signs content, and DMARC tells receivers how to evaluate alignment. They solve different parts of the trust problem.

The table is my decision record, not a vendor scorecard.

Option Evidence you can retain Failure boundary Use it when
Direct SMTP from the app Your own SMTP logs and queue state IP reputation, retries, and feedback are your responsibility You operate mail infrastructure and compliance tooling
Transactional email API Request IDs, event payloads, DNS checks Provider policy, regional availability, and event semantics You need managed delivery with an exportable audit trail
Shared marketing platform Campaign and unsubscribe records Transactional traffic can share reputation or policy controls The same team owns both marketing and support communications

There is no universally correct pick. For a small team, a managed API usually reduces operational surface; for a regulated deployment, the deciding question is whether events, retention, and regional processing meet your evidence requirements.

How should a Node.js SaaS handle password reset email deliverability?

Treat the mail call as the last step in a state transition. The application first validates the request, creates a random token hash, and writes an audit row. Only then does it evaluate domain readiness and suppression. A queued job can retry a transient response, but it must not mint a second token for every retry.

Here is the critical path in Python-like pseudocode (the same boundaries apply in a Node.js worker):

from datetime import datetime, timedelta, timezone
import hashlib
import secrets

def request_reset(user_id, email, db, mailer, clock=datetime.now):
    now = clock(timezone.utc)
    token = secrets.token_urlsafe(32)
    token_hash = hashlib.sha256(token.encode("utf-8")).hexdigest()

    db.audit("reset.requested", user_id=user_id, at=now)
    db.store_reset(user_id, token_hash, expires_at=now + timedelta(minutes=20), used=False)

    # Keep the external response generic, even when the address is unknown.
    if db.is_suppressed(email):
        db.audit("reset.suppressed", user_id=user_id, reason="hard_bounce", at=now)
        return {"accepted": True}

    if not db.domain_is_verified("support.example"):
        db.audit("reset.deferred", user_id=user_id, reason="domain_unverified", at=now)
        return {"accepted": True}

    job = db.enqueue_mail(
        to=email,
        template="password-reset-v3",
        variables={"token": token},
        idempotency_key=f"reset:{user_id}:{token_hash}",
    )
    mailer.submit(job)
    db.audit("reset.queued", user_id=user_id, job_id=job.id, at=now)
    return {"accepted": True}
Enter fullscreen mode Exit fullscreen mode

The response stays generic, while internal events remain specific. That separation matters for account enumeration and for audits. The token is stored as a hash, and the raw value appears only in the message payload. I use a 20-minute example window here; your threat model may justify less, and I'm not sure a single duration fits every support product.

Domain verification and DKIM are rollout gates

Create a small domain inventory with owner, DNS record, environment, verification date, and evidence location. SPF should have one coherent policy for the envelope domain. DKIM keys need rotation ownership and a selector history. DMARC alignment should be tested with representative From addresses, including the address used for queue notifications.

The deployment pipeline can fail closed when a required record is absent. That does not mean rejecting the user's reset request; it means recording the request and deferring the send until the domain is ready. A reviewer can then see a clean chain: request accepted, message deferred, DNS evidence attached, message released.

For contact forms, preserve the original sender in a Reply-To field and keep the authenticated From address on your domain. Do not let arbitrary form input become a From header. Normalize Unicode domains, enforce length limits, and reject header characters before templating. These are small checks with large consequences for spoofing and queue routing.

A hard bounce is a reliability state, not a retry hint

Bounce processing is a state machine, not a webhook-shaped if statement. Classify provider feedback into transient, permanent, and policy categories. A 550 5.1.1 mailbox-not-found response is a strong permanent signal; a temporary deferral should retain the job with bounded backoff. Store the original event, classification, and classifier version so a later rule change is explainable.

The send path checks suppression atomically with enqueue. Otherwise two workers can both observe an address as clean and submit duplicate reset messages after the first hard bounce. The suppression key should be normalized address plus tenant, unless your compliance policy requires a global block.

For support queues, include a one-click operator action to mark an address as invalid and a separate action to release a false positive. Release requires a reason and actor. Do not silently delete suppression rows; append a state change and retain the prior evidence for the period your policy requires.

Soft bounces need a limit. Five retries over an hour is a reasonable starting policy, but monitor the distribution before changing it. Permanent failures should stop retries immediately. A password reset message that arrives six hours late is often worse than a clear, generic response that asks the user to try again.

Which data retention evidence survives a compliance review?

I run tests against the evidence, not only the happy-path inbox:

  1. A verified domain sends a reset and produces one audit chain from request to provider event.
  2. An unverified domain records a deferral and never calls the mailer.
  3. A hard bounce suppresses the next contact-form notification and reset message for that tenant.
  4. Duplicate queue deliveries with the same idempotency key create one outbound message.
  5. A malformed Reply-To value is rejected without changing the authenticated From header.
  6. An expired or already-used token cannot reset the account.

Export these cases as JSON fixtures. Include timestamps, event IDs, tenant IDs, template versions, and the exact DNS evidence reference. Metrics such as acceptance rate, permanent-bounce rate, retry age, and suppression hits tell the operations team where to look; they do not replace the underlying records.

The catch is operational cost: evidence retention, DNS rotation, and feedback classification require an owner. This approach is not suitable when a team cannot monitor a queue or meet its retention obligations. Stick with a simpler password-reset mechanism, or a provider with built-in compliance exports, when those controls are outside your operating model; the trade-off is less control over routing and data locality.

References

Top comments (0)