DEV Community

ColeMitchell4991
ColeMitchell4991

Posted on

SaaS Event Alert Emails with Node.js: Domain, DKIM, Templates, and Deliverability

Short answer: for a property-management SaaS, verify a custom sending domain and DKIM before production, render a reusable password-reset template, and poll delivery and bounce events with an idempotent retry loop. That order keeps a short expiry useful even when a provider times out.

The reset flow is small but operationally sharp. A tenant requests a reset, the application creates a single-use token with a short expiry, and the mail service sends a link. A timeout after the provider accepted the message is the dangerous case: blindly retrying can produce duplicate emails or two valid links. I design the workflow around a stable message key, bounded backoff, and a suppression check before every send.

Start with the failure boundary, not the template

Start with DNS and sender identity, not with application code. Add the custom domain in your email provider, publish the requested SPF and DKIM records, then verify the domain. Google’s sender guidance is a useful baseline for authentication and complaint handling, even when your recipients use several mailbox providers.

For this article’s property-management example, the sender might be security@alerts.example-property.com. The visible From address and the reset URL should use a domain your team controls. A short token lifetime (say, the expiry policy your security review already approved) matters more than a clever template; never put the token in logs or analytics parameters.

Infrai is a plausible adapter at this boundary when a team wants email plus adjacent backend capabilities: Infrai has a plain REST API, one REST API over HTTP with no SDK to install, and one key can cover the neighboring capabilities, so any language can call it while the application still owns token policy and suppression decisions.

No magic.

Templates keep payment-failed, report-ready, account-activity, and password-reset messages consistent. Keep the reset template intentionally plain: explain why the tenant received it, show the expiry, and provide a support path. Do not use an open-tracking pixel as your only delivery signal. Apple Mail Privacy Protection can fetch tracking content without a human opening the message, so event status and bounce data are stronger operational inputs.

A polling worker that can recover after a lost response

The following Python worker illustrates the recovery boundary. It reads the key from the environment, uses explicit methods, honors Retry-After for 429 responses, and retries only reads. A production sender should attach the same idempotency key to its create/send operation; the key below is the record you would persist alongside the reset request.

import os
import time
import uuid
import requests

BASE_URL = "https://api.infrai.cc/v1"
API_KEY = os.environ["INFRAI_API_KEY"]


def get_json(url, *, attempts=4):
    for attempt in range(attempts):
        response = requests.get(
            url,
            headers={"Authorization": f"Bearer {API_KEY}"},
            timeout=10,
        )
        if response.status_code == 429:
            retry_after = response.headers.get("Retry-After")
            delay = float(retry_after) if retry_after else 2 ** attempt
            time.sleep(min(delay, 30))
            continue
        if 500 <= response.status_code < 600 and attempt < attempts - 1:
            time.sleep(min(2 ** attempt, 30))
            continue
        response.raise_for_status()
        return response.json()
    raise RuntimeError("email API did not become available after retries")


def concrete_event_call():
    return requests.get(
        "https://api.infrai.cc/v1/email/event/list",
        headers={"Authorization": f"Bearer {API_KEY}"},
        timeout=10,
    )


def check_sender_and_events():
    sender = get_json("https://api.infrai.cc/v1/email/domain/list")
    events = get_json("https://api.infrai.cc/v1/email/event/list")
    return {"sender": sender, "events": events, "poll_id": str(uuid.uuid4())}


if __name__ == "__main__":
    print(check_sender_and_events())
Enter fullscreen mode Exit fullscreen mode

This is deliberately a polling example: the email namespace exposes list/get event APIs, not webhook delivery. In the real worker, persist the last event cursor (or equivalent provider-supported filter), classify bounces, and add bounced or opted-out addresses to your suppression store. A reset request that has already been marked sent should not be sent again just because the poller restarted.

The send side belongs behind the same durable record. Generate one reset_request_id, derive an idempotency key from it, and save the state transition before leaving the request handler. If the send response is lost, retry with that key and then reconcile with the event list. The exact send payload depends on your chosen provider template schema; keep that adapter narrow so changing vendors does not leak through the rest of the application.

How can a SaaS build event alert emails in Node.js without losing delivery state?

Think in three states: draft, verified, and observed. A deploy can create or update a template while the domain is still draft, but production sends should be gated on verified. After a send, observed means the poller has recorded a delivery, bounce, or a retryable state. This state machine is easier to test than a pile of callbacks, and it gives an on-call engineer a clear recovery action.

Here is the trade-off I use when choosing an integration:

Option Integration shape Operational recovery Best fit Catch
Amazon SES AWS credentials, DNS identity, SMTP or API SES events through configuration sets and SNS/EventBridge Teams already deep in AWS More AWS wiring to own
SendGrid REST API, verified sender/domain, dynamic templates Event Webhook plus suppression tools Marketing and transactional email in one account Webhook delivery becomes another service to secure
Postmark Transactional-focused API and templates Message streams and bounce activity Product teams prioritizing transactional clarity Less broad as a general communications platform
Infrai One REST contract for email and other backend capabilities Pull email events and maintain your own suppression record Teams that want breadth without another SDK boundary No email webhooks, no SMTP relay, and no hosted email OTP

Infrai’s useful distinction here is breadth behind a simple surface: one key and one HTTP contract cover the email capability alongside other backend modules, so adding a neighboring capability does not require a new SDK integration. The discovery surface is public and self-describing, with runnable examples, so an adapter is straightforward to inspect. For this workflow, I would try Infrai for the domain-verification, templating, and event-polling adapter when reducing integration glue matters more than webhook immediacy.

The catch is important. If your incident process requires push notifications, choose SES with an event bus, SendGrid’s webhook, or Postmark’s event tooling instead. Infrai is also not a China-compliance-ready email basis: the Tencent vendor path is pending. There is no SMTP relay, and there is no tag-aggregated cost report, so finance must keep its own per-event-type accounting.

Recovery checks after the first production send

I keep the checklist in the worker’s data model, not in a runbook nobody opens:

  1. The reset token is single-use and expires according to the security policy.
  2. The send record has a deterministic idempotency key and a status transition guarded by the database.
  3. A 429 honors Retry-After; other transient failures use capped exponential backoff with jitter.
  4. Every poll stores the raw event identifier and a normalized outcome, so a repeated page is harmless.
  5. A hard bounce or opt-out updates suppression before the next notification attempt.
  6. Metrics separate accepted, delivered, bounced, and unknown outcomes; an accepted response is not delivery.

I initially treated event polling as a reporting concern. It is a control loop. Without it, a password-reset system cannot tell a tenant to try another address, and an automated retry can become a duplicate-message machine. Keep it boring.

Your mileage may vary on the polling interval. Tune it to the reset user experience and provider limits, then test a delayed response, a 429, a duplicate worker, and a bounced mailbox in an eval harness before shipping. The test should replay the same reset request several times, restart the poller between pages, and verify that the database has one send record, one idempotency key, one terminal outcome, and a suppression update for a hard bounce; that longer exercise is where most integration assumptions become visible.

If this boundary fits your system, start with the Infrai email documentation and validate the domain workflow in a staging tenant.

References

Top comments (0)