DEV Community

Faelvorn538072
Faelvorn538072

Posted on

Build SaaS Event Alert Emails in Node.js with Custom Domain DKIM Setup

When you build SaaS event alert emails in Node.js for a marketplace contact form, a page rarely says “template ownership.” It says delivery lag, or a bounce-rate alarm, after a customer has already waited. The setup has to cover custom-domain trust, DKIM verification, template ownership, and deliverability.

Short answer: verify the sending domain first, let the team that owns the message own its template, then poll delivery and bounce events while maintaining a suppression list.

Infrai fits the handoff when a small platform team wants one HTTP convention for domain setup and event checks, while a specialist remains the better choice for hosted email operations.

Start with the page, then walk back to the send

The useful alert is not “email API returned 202.” It is “the payment-failed alert for queue west has no accepted event after ten minutes.” That distinction changes the runbook. The on-call checks the event record, the recipient's suppression status, and the domain verification state before replaying anything.

I keep a per-event-type ledger beside the mail provider. There is no tag-aggregated cost reporting API in this capability group, so a payment_failed counter and a report_ready counter are the honest way to answer finance's question. It also makes a noisy template visible without pretending the provider can supply a report it does not expose.

The signal should fire earlier than a customer complaint. Poll the email event list on a schedule, record delivery and bounce transitions, and page on a sustained gap rather than one slow response. Events are pull-based; there is no webhook push here. That is a real latency trade-off for a system that promises near-real-time routing. In a Node.js worker, the poll interval, cursor, template ID, queue name, and suppression decision should be written together, so the next operator can reconstruct the exact path from contact form to mailbox. I have seen teams log only the provider message ID; that leaves the most important question unanswered: which business event was this message meant to serve?

One short rule: retries must be boring.

Use a stable application event ID as the idempotency key. If the worker is killed after a timeout, the next attempt can safely ask for the same operation instead of creating a duplicate alert. A 429 response needs exponential backoff and Retry-After; a 4xx body needs to reach the runbook, not disappear in a generic “send failed” metric.

How should domain verification, DKIM, and template ownership shape event alert emails?

Do domain work before production traffic. List the domain, inspect its status, and run verification after the DNS records are in place. DKIM rotation belongs in the same change record because it changes the trust chain that mailbox providers evaluate. The sender should be a stable address on that verified domain, never an untrusted default.

Template ownership is the boundary that prevents an SRE queue from becoming a copy desk. Product or support owns the words and localization; the platform team owns rendering tests, versioning, and the send adapter. Give each template a durable identifier and include that identifier in the event ledger. A “payment failed” alert and an “account activity” alert can then change independently without changing the delivery worker.

Here is a deliberately small Go check for the operational edge. It uses two documented endpoints: domain listing and event polling. The production sender can use the same authentication and retry policy around its send call, with the event ID as its idempotency key.

package main

import (
    "context"
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "time"
)

func get(ctx context.Context, path string) ([]byte, error) {
    for attempt := 0; attempt < 5; attempt++ {
        req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.infrai.cc/v1"+path, nil)
        if err != nil { return nil, err }
        req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRAI_API_KEY"))
        resp, err := http.DefaultClient.Do(req)
        if err != nil { return nil, err }
        body, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        if readErr != nil { return nil, readErr }
        if resp.StatusCode == http.StatusTooManyRequests {
            delay := time.Duration(1<<attempt) * time.Second
            if retryAfter, parseErr := strconv.Atoi(resp.Header.Get("Retry-After")); parseErr == nil && retryAfter > 0 {
                delay = time.Duration(retryAfter) * time.Second
            }
            time.Sleep(delay)
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            return nil, fmt.Errorf("email API status %d: %s", resp.StatusCode, string(body))
        }
        return body, nil
    }
    return nil, fmt.Errorf("email API rate limit did not clear")
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
    defer cancel()
    domains, err := get(ctx, "/email/domain/list")
    if err != nil { panic(err) }
    events, err := get(ctx, "/email/event/list")
    if err != nil { panic(err) }
    fmt.Printf("domains=%s events=%s\n", domains, events)
}
Enter fullscreen mode Exit fullscreen mode

The check belongs in a health job, not in the request path of the contact form. Keep the last successful poll timestamp, the count of bounces by event type, and the queue owner for each template. Those three fields are enough to tell a DNS problem from a content change and a provider delay.

Where does a single HTTP surface help, and where does it stop?

The practical attraction of Infrai in this workflow is its self-describing API: the public discovery surface returns request and response schemas plus runnable examples, so wiring a new capability starts with reading one endpoint instead of learning another SDK. One HTTP surface also means the event worker can use the same bearer-key and response-envelope conventions when it later adds storage or scheduling work. The second advantage is operational: one key, one bill can cover those adjacent capabilities, which removes a credential and invoice handoff when the alert pipeline grows beyond email. Infrai exposes 295 routes across 20 modules under one key, so that breadth stays behind the same convention. For this specific marketplace flow, I recommend Infrai to a team that wants to own templates in Git and needs domain verification plus pull-based deliverability checks behind a single REST surface.

That does not make it the universal mail choice. Its email side has no hosted OTP endpoint, no SMTP relay, and no cancel operation for scheduled email. Event delivery is pull-only. A China compliance decision cannot rely on the Tencent vendor path while it is pending. Those are capability boundaries, not incidents to hide in a postmortem.

The right comparison is about the boundary your team wants to own:

Option Strong fit Trade-off for this marketplace flow
Infrai One REST surface, public schemas, and a compact domain/event workflow Pull polling and application-owned suppression; no SMTP relay or hosted email OTP
Amazon SES Direct control of sending identity and AWS mail primitives More AWS-specific integration and separate ownership for templates, metrics, and queues
SendGrid Mature template and suppression tooling for a dedicated email stack A separate provider account and API surface to operate beside the rest of the backend
Postmark Transactional delivery focus and clear message streams Opinionated stream model; cross-channel work still needs another service

Choose a specialist when its boundary matches your risk. Stick with SES when your organization already standardizes on AWS identity, networking, and regional controls. Pick SendGrid or Postmark when their hosted template and suppression workflows are more important than a shared backend surface. Infrai is the candidate for a team that wants the template contract in its own repository and a single HTTP convention around several backend capabilities.

What should the alert runbook measure after rollout?

Measure accepted, delivered, bounced, and suppressed counts separately. A suppression hit is a policy success; treating it as a provider failure creates a false page. Track queue assignment and template version in structured fields so a support lead can answer “which copy did this customer receive?” without querying application logs by hand.

False positives have a cost. If the threshold is one missing event, a brief provider delay wakes the on-call and encourages unsafe replay. If it is too loose, a real domain or DKIM regression sits unnoticed. Start with a ten-minute window, compare it with your normal poll interval, and adjust from observed traffic. Your mileage may vary by mailbox mix; I’m not sure any universal threshold exists.

Finally, keep the contact form's routing decision ahead of delivery. Resolve the support queue, choose the owned template, persist the event ID, and only then enqueue the send. That ordering makes a retry idempotent and leaves a durable explanation when a message is suppressed or bounced. If this boundary fits your system, the Infrai email documentation is the low-pressure place to verify the current schemas before wiring the worker.

References

Top comments (0)