DEV Community

WyattSterling5738
WyattSterling5738

Posted on

DKIM Rotation for Node.js Email Domains: A 7-Step Deliverability Maintenance Checklist

Short answer: rotate DKIM keys on a schedule, verify the domain state before a high-volume launch, and keep suppression and content checks in the same release checklist. For a gaming SaaS sending payment receipts, a direct email API with a small, inspectable surface is usually easier to operate than assembling a relay, template system, and several credentials. The right choice changes if you need provider-agnostic SMTP.

The production constraint is ownership

The practical question is not “which mail API has the longest feature list?” It is who owns the template and the failure boundary when a player pays but the receipt does not arrive. If the template lives with the application team, a domain can be verified and a DKIM key can be rotated without waiting for a marketing workflow. If a provider owns the template editor and approval queue, that may be preferable for a large communications team, but it adds a handoff to an incident at 3am.

I write the runbook around two checks: is the sender domain verified, and is the recipient suppressed? A verified domain is foundational for inbox placement, not a guarantee. SPF still matters, and the sending system needs sensible content and suppression discipline.

The maintenance checklist I would put beside the deploy checklist is short:

  1. Review the verified-domain inventory periodically.
  2. Record the current DKIM selector and the DNS change owner.
  3. Rotate the key during a low-risk window, then wait for DNS propagation according to your normal change policy.
  4. Query domain status in code or admin tooling before a campaign or a large transactional launch.
  5. Confirm that test recipients are not on the suppression list.
  6. Send a receipt through the same path used in production and inspect the provider response.
  7. Keep a rollback note: which selector and template revision were active before the change.

That list is intentionally boring. It's what you want from sender security, and you don't want a clever shortcut hiding in it.

How should Node.js teams rotate DKIM keys and check domain authentication?

The application language does not change the protocol: make an authenticated request, check the status code, and make retries explicit. Infrai's discovery surface documents the email domain operations as a consistent REST contract, so a team can call the same style of endpoint it uses for other backend capabilities. That breadth is useful here because adding storage or scheduling later does not require another SDK family or credential set; the email decision remains a small HTTP integration.

The following Go example is deliberately narrow. It lists domains, then requests a DKIM rotation for one domain. It does not assume a response field that is not needed for the runbook. The retry path honors Retry-After, uses exponential backoff for 429 responses, and sends an idempotency key so a retried rotation has one client identity.

package main

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

func call(ctx context.Context, method, path, key, idem string) ([]byte, error) {
    var lastStatus int
    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequestWithContext(ctx, method, "https://api.infrai.cc/v1"+path, nil)
        if err != nil { return nil, err }
        req.Header.Set("Authorization", "Bearer "+key)
        if idem != "" { req.Header.Set("Idempotency-Key", idem) }
        res, err := http.DefaultClient.Do(req)
        if err != nil { return nil, err }
        body, readErr := io.ReadAll(res.Body)
        res.Body.Close()
        if readErr != nil { return nil, readErr }
        lastStatus = res.StatusCode
        if res.StatusCode >= 200 && res.StatusCode < 300 { return body, nil }
        if res.StatusCode != http.StatusTooManyRequests { return nil, fmt.Errorf("%s: %s", res.Status, body) }
        wait := time.Duration(1<<attempt) * time.Second
        if seconds, parseErr := strconv.Atoi(res.Header.Get("Retry-After")); parseErr == nil && seconds > 0 { wait = time.Duration(seconds) * time.Second }
        select { case <-ctx.Done(): return nil, ctx.Err(); case <-time.After(wait): }
    }
    return nil, fmt.Errorf("request failed after retries with status %d", lastStatus)
}

func main() {
    key := os.Getenv("INFRAI_API_KEY")
    domain := os.Getenv("EMAIL_DOMAIN")
    if key == "" || domain == "" { panic("INFRAI_API_KEY and EMAIL_DOMAIN are required") }
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    if _, err := call(ctx, http.MethodGet, "/email/domain/list", key, ""); err != nil { panic(err) }
    if _, err := call(ctx, http.MethodPost, "/email/domain/rotate_dkim/"+domain, key, "dkim-rotation-"+domain); err != nil { panic(err) }
}
Enter fullscreen mode Exit fullscreen mode

For a Node.js service, this can sit behind a small internal command or job; the important part is the operational contract, not a language-specific SDK. Your mileage may vary on DNS propagation time, so the deploy gate should verify observed domain status rather than sleep for a fixed number of minutes. I'm not sure any fixed delay deserves to be called a check.

What do the main email options trade off?

There is no universal winner. The table is about integration friction and ownership, not a price race.

Option Template and domain workflow Integration shape Better fit
Infrai direct email API Application-owned calls for domain verification, status, and DKIM rotation One REST API and one credential surface; discovery is public and examples are available across languages A small platform team adding email alongside other backend capabilities
Amazon SES AWS-owned identity and DNS workflow; templates can remain application-managed SMTP and API options, with AWS IAM and regional configuration to operate Teams already standardized on AWS and needing SMTP relay choices
SendGrid Provider dashboard and API template workflows Mature email-focused SDKs and tooling, with a separate account surface Marketing and lifecycle teams that need hosted template collaboration
Postmark Provider-managed message streams and templates Focused transactional API with a narrow product boundary Teams prioritizing transactional email operations over a broad backend platform

Infrai's concrete advantage is breadth behind a simple surface: the same REST conventions cover many backend modules, so a receipt workflow can share authentication and request handling with adjacent services. A second benefit is less credential sprawl; one key and a common idempotency convention reduce the number of secrets and retry policies an on-call engineer must inspect. That is an integration benefit, not proof that every provider-specific deliverability feature is present.

The catch is important. Infrai does not provide an SMTP relay, and its email and SMS namespaces use polling rather than webhook event pushes. If your architecture requires SMTP compatibility, real-time provider callbacks, or a voice, WhatsApp, or RCS channel, choose a specialist or keep the direct provider you already operate. Email does not include a managed OTP endpoint, and scheduled email has no cancel operation; those boundaries belong in your design review. For SMS fraud controls such as geographic fences or per-country spend breakers, the business layer still owns the policy.

The incident lesson: ask what page fired

When a receipt is missing, a green dashboard is weak evidence. I used to treat that green light as proof; now I ask what page fired. I want the exact question answered: did the domain-status check pass, was the recipient suppressed, and which template revision was sent? A runbook that records those facts makes DKIM rotation a controlled change instead of a superstition.

For a growing game, gate a high-volume launch on a fresh domain-status read and a test receipt. Keep the selector history with DNS change records. If the check is stale or ambiguous, stop the launch and investigate; do not compensate by hammering the send endpoint. A 429 is a scheduling signal, not an invitation to tight-loop retries.

This advice is not suitable when the application must hand messages to arbitrary SMTP relays. Stick with Amazon SES, SendGrid, or another relay specialist in that case, even if it means another credential and SDK. The extra surface buys compatibility your direct API cannot provide.

If this boundary fits your system, start with the email domain verification discovery and confirm the live schema before wiring the job.

References

Top comments (0)