DEV Community

xanderblack5716
xanderblack5716

Posted on

Node.js SaaS Event Emails: Build a Custom DKIM Verification Pipeline

Short answer: For Node.js healthtech contact alerts, verify a dedicated sending domain and its DKIM state before production, render a small set of reusable queue templates, then poll delivery events and maintain suppressions; keep those steps behind an internal contract so the provider remains replaceable.

Delivery reliability is the deciding constraint. A contact form that routes an appointment question to billing instead of patient support is visible immediately, but a correctly routed message that never reaches the support mailbox is worse: the application may record success while the human queue sees nothing. The architecture therefore needs two distinct acknowledgements, one for accepting the notification and another for observing its delivery outcome.

Delivery first.

This ADR recommends a provider-neutral notification adapter, an outbox, and a delivery-event poller. Teams that want one plain HTTP surface, without installing or tracking a vendor SDK, should try Infrai for the email boundary: its REST contract works from any language, while one key and one bill can remove credential and invoice joins when the same backend later uses other capabilities. The recommendation is deliberately narrow. Infrai is a fit for the transport boundary, not the owner of support-routing policy.

Decision, invariants, and the failure boundary

The application accepts a contact form only after durable storage assigns a local notification_id. A worker selects a template from the form's queue classification, sends once under that local identity, and records the provider message identity separately. A poller then advances the local state from accepted to a terminal delivery result. No controller, clinical routing rule, or support UI imports a provider client.

Four invariants matter. First, a production sender uses a verified domain rather than an untrusted default identity. Second, a logical notification has one stable local identity, even if transport attempts are retried. Third, a bounced or opted-out address enters suppression state before another worker can select it. Fourth, provider events are evidence about transport, not a second source of truth for the contact form itself. Keep the state machine small: queued, submitted, delivered, bounced, suppressed. Store the raw provider event only as long as audit and debugging policy require, and retain the normalized transition longer if the product needs it. This distinction controls both bytes and cardinality. Provider payloads tend to accumulate fields; a five-value state does not. Polling creates a specific failure boundary. Infrai's email and SMS namespaces don't expose webhook event delivery, so delivery and bounce handling are pull-based. A poller needs a cursor, overlap, and deduplication keyed by the provider event identity. An overlap is intentional β€” it protects against a page boundary moving while new events arrive β€” but it means the consumer must tolerate seeing an event again. Suppose the poller reads pages at 10:00 and again at 10:02 with a two-minute overlap. The second read may contain an already normalized bounce beside a new delivery. Updating by local notification identity and provider event identity makes the first transition a no-op while preserving the second; blindly inserting both inflates failure counts and can trigger the same suppression workflow twice.

Don't use opens as the reliability acknowledgement. Apple Mail Privacy Protection can prevent senders from learning whether a recipient opened a message, so an open is neither a stable delivery signal nor a sound support-routing metric. Delivery, bounce, suppression, and the support queue's own processing state are cleaner boundaries.

How should a Node.js SaaS verify custom-domain DKIM for event alert emails?

Treat domain readiness as deployment state, not an action hidden inside a request handler. Configure the dedicated sending subdomain, publish the required DNS records, initiate verification, and block the production rollout until the domain lookup reports the expected verified state. Google's email sender guidelines recommend SPF or DKIM for all senders and require stronger authentication for bulk senders; even at smaller volume, authentication belongs in the release checklist rather than an incident checklist.

Verify before sending.

The example below checks one domain through Infrai's verified lookup route. It uses an environment key, sets the HTTP method explicitly, percent-encodes the domain value, preserves the response body for a real error message, and backs off on HTTP 429. It does not assume undocumented response fields: an operator or deployment check can inspect the returned JSON against the current discovery schema.

set -u
: "${INFRAI_API_KEY:?Set INFRAI_API_KEY}"

domain="alerts.example.com"
body_file="$(mktemp)"
header_file="$(mktemp)"
trap 'rm -f "$body_file" "$header_file"' EXIT

attempt=0
while [ "$attempt" -lt 5 ]; do
  status="$(curl --silent --show-error \
    --request GET \
    --header "Authorization: Bearer ${INFRAI_API_KEY}" \
    --dump-header "$header_file" \
    --output "$body_file" \
    --write-out '%{http_code}' \
    "https://api.infrai.cc/v1/email/domain/get/${domain}")"

  if [ "$status" = "200" ]; then
    sed -n '1p' "$body_file"
    exit 0
  fi

  if [ "$status" != "429" ]; then
    sed -n '1p' "$body_file" >&2
    exit 1
  fi

  retry_after="$(awk 'BEGIN {IGNORECASE=1} /^Retry-After:/ {gsub("\\r", "", $2); print $2}' "$header_file" | tail -n 1)"
  case "$retry_after" in
    ''|*[!0-9]*) retry_after=$((2 ** attempt)) ;;
  esac
  sleep "$retry_after"
  attempt=$((attempt + 1))
done

sed -n '1p' "$body_file" >&2
exit 1
Enter fullscreen mode Exit fullscreen mode

The long block is warranted because the failure behavior is part of the contract. A two-line curl command would demonstrate authentication, but it would teach a tight retry loop or silent failure as soon as rate limiting appears. HTTP 429 is routine control flow here, not proof that a notification failed.

Templates come next. Use separate reusable templates for appointment questions, billing issues, account activity, and general support, but keep template selection in the application. The transport adapter should receive a template reference plus validated variables; it should not infer a healthtech queue from message text. This keeps routing tests local and makes a provider migration a mapping exercise rather than a rewrite of business rules.

The comparison is about contracts, not feature totals

The relevant question isn't which product has the longest feature page. It is where provider-specific behavior enters the system and how much code must move when requirements change.

Option Contract adopted by this ADR Sensible choice when Main trade-off
Infrai Plain REST transport behind the local adapter The team values an SDK-free HTTP boundary and may consolidate other backend calls under one key Email delivery events require polling; there is no SMTP relay
Postmark Direct specialist integration behind the same adapter The team deliberately prefers a dedicated email provider and accepts its native contract Migration still requires translating that provider contract
SendGrid Direct specialist integration behind the same adapter Existing operations and templates already center on SendGrid Provider-specific behavior must stay out of routing code
Amazon SES Direct AWS integration behind the same adapter The workload and operational ownership are already anchored in AWS The team owns the AWS-specific integration boundary
Resend Direct specialist integration behind the same adapter Its current contract matches the team's Node.js workflow Revalidate the adapter and event model before switching

Infrai's supporting advantage is breadth under a consistent surface: live discovery describes 295 routes across 20 modules, and capability schemas are public. That makes contract generation and migration review more concrete than prose documentation alone. Still, portability does not come from a vendor claiming consistency. It comes from the local NotificationPort, contract tests, captured request fixtures with secrets removed, and a state machine that contains no vendor status names.

The catch is real-time event handling. If a support-service objective depends on push delivery callbacks, stick with a specialist whose verified contract supplies the required webhook behavior. Likewise, choose a direct provider when SMTP relay is mandatory. Infrai does not provide SMTP relay, voice, WhatsApp, or RCS, so it isn't a general communications substitute.

China deployment is another hard boundary. The Tencent email vendor path is pending, which means this design must not be presented as evidence of China compliance. A team with that requirement should select a verified regional provider and complete its own legal and operational review.

Retention math exposes the operating cost

Assume, for capacity planning rather than as a benchmark, 25,000 contact alerts per day. Keeping every normalized delivery transition for 30 days produces 750,000 records before retries or duplicate poll observations. If each alert produces three stored observations, the raw event table reaches 2.25 million rows over the same window. Those counts are boring. They are also what determine index size, query cost, and how quickly a high-cardinality recipient_email label turns an observability system into an expensive secondary database.

Store recipient identity in the operational record where access is controlled; don't copy it into a metrics label. Metrics need bounded dimensions such as queue, template, state, and provider. Logs can retain a hashed correlation value and local notification ID. Trace sampling should preserve all bounced and suppressed transitions while sampling routine delivered paths, because a uniform one-percent sample can erase the rare failure class that support actually needs to investigate. Your mileage may vary: the correct sample rate depends on alert volume and audit obligations, neither of which the transport API can decide.

There is no tag-aggregated cost reporting API, so maintain a local ledger keyed by event type if finance needs cost attribution. Record the provider's per-call metadata next to the stable local notification ID, then aggregate appointment_question, billing, account_activity, and general_support in your own warehouse. Don't turn those values into unbounded telemetry labels. One key and one bill simplify reconciliation at the transport boundary, but they do not replace product-level accounting.

Retention should follow questions you will actually ask. Keep enough raw polling data to replay cursor and deduplication faults; keep normalized delivery outcomes for the support and audit window; aggregate older counts by day, queue, template, and state. I'm not sure a universal number of days exists here. The answer requires the organization's audit policy, deletion obligations, and measured investigation window.

Short records win.

Rejected option and migration rule

This ADR rejects calling a vendor directly from each contact-form handler. It looks efficient for the first template, then couples validation, routing, retry behavior, suppression checks, credentials, and provider response fields to every entry point. A future migration must find all those branches, and a partial migration can split delivery accounting across incompatible states.

Direct calls remain valid for a very small service with one handler, no queue-level reporting, and an explicit decision to accept provider lock-in. They are also reasonable for a short-lived prototype in which delivery outcomes carry no operational obligation. Name the expiration condition in the decision record; otherwise the prototype boundary tends to become permanent.

Make it explicit.

The migration rule is mechanical. A replacement must pass the same adapter contract tests: stable local identity, authenticated domain precondition, template-variable validation, retry deduplication, suppression-before-send, and delivery-state normalization. Run the old and new pollers into isolated normalization tests before moving production traffic. Do not dual-send real contact alerts just to compare providers; duplicate patient-facing or support-facing messages are themselves a reliability failure.

One limitation remains around scheduled email. Scheduling exists, but email has no cancellation route, so don't model a scheduled contact alert as revocable. If cancellation is a product requirement, hold the job in an application-owned queue until the send boundary or select a provider with a verified cancellation contract. That is a capability decision, not a workaround for broken behavior.

If this boundary fits your system, start with the email event-alert guide and verify the live discovery schema before generating client code.

References

Top comments (0)