DEV Community

EliBennett128
EliBennett128

Posted on

Custom-Domain DKIM Email and SMS Alerts for Product Event Notifications

Short answer: compare Postmark, Resend, SendGrid, and SES with an SMS provider for a fintech signup verification link; choose a combined API when integration effort dominates, or separate specialists when SMTP and pushed events matter more.

Architecture Candidates Best fit Cost of the choice
Email specialist plus SMS specialist Postmark, Resend, SendGrid, or Amazon SES plus Twilio Existing email operations or a channel-specific runbook Two provider boundaries to configure, secure, and observe
Combined email and SMS API Infrai, where one API key covers email, SMS, and the broader capability surface API-first product events where a smaller integration surface matters Pull-based event handling and fewer specialist workflow features

My default for a new US/EU signup flow is the combined shape. Its concrete integration advantage is breadth behind one consistent REST contract: email and SMS sit alongside other backend capabilities under one key, so adding another capability doesn't require another SDK. This is a control-plane decision, not a claim that one transport has universally better deliverability.

The runner-up is a dedicated email provider paired with Twilio. Keep that shape when a working SMTP pipeline already exists, when webhook delivery is part of the response-time budget, or when email is important enough to deserve its own provider adapter and operating model.

Reliability starts before the first notification

A verification notification is one product event with several possible delivery actions. Model it that way. The application creates a short-lived verification link, records a notification identifier, sends the branded email, and decides from explicit policy whether an SMS alert is immediate, delayed, or unnecessary. That policy should live in application code because geography, abuse risk, and customer consent are product concerns rather than transport details.

This gets subtle fast. Both email and SMS have suppression management, which is useful for avoiding repeated sends to bad recipients, but the two channels should not share a single boolean called suppressed. A rejected email address says nothing about whether a phone number is valid. Store suppression and delivery state per channel, retain the provider message identifier, and make every transition idempotent. If a worker sees the same signup event twice, it must not create two independent notification attempts.

Keep the state machine boring.

Then test it.

For branded email, domain verification and DKIM rotation belong in deployment operations, not in the customer request path. DKIM lets a receiving system validate responsibility for a message through a signing domain. It doesn't promise inbox placement. A fair deliverability comparison among Postmark, Resend, SendGrid, SES, and a combined provider therefore needs controlled traffic with the same domain class, recipient mix, message content, and sending pattern.

I'm not sure a static vendor ranking can answer that part honestly. The available evidence establishes domain verification, DKIM rotation, and suppression hygiene for the combined option; it does not establish comparative inbox rates. Your mileage may vary — particularly across US and EU mailbox providers — so define an acceptance test before choosing a winner.

Implement the custom-domain readiness gate

The useful code sample here is not another generic send call. It is a deployment check that confirms the custom domain is present before signup traffic is enabled. The route below is verified, the HTTP method is explicit, credentials stay in environment variables, non-success responses are surfaced, and HTTP 429 honors Retry-After before exponential backoff.

const apiKey = process.env.INFRAI_API_KEY;
const apiBaseUrl = process.env.COMMS_API_BASE_URL;
const sendingDomain = process.env.SENDING_DOMAIN;

if (!apiKey || !apiBaseUrl || !sendingDomain) {
  throw new Error("Set INFRAI_API_KEY, COMMS_API_BASE_URL, and SENDING_DOMAIN");
}

const sleep = (milliseconds: number) =>
  new Promise((resolve) => setTimeout(resolve, milliseconds));

async function getSendingDomain(domain: string): Promise<unknown> {
  const path = `/v1/email/domain/get/${encodeURIComponent(domain)}`;
  const url = new URL(path, apiBaseUrl);

  for (let attempt = 0; attempt < 4; attempt += 1) {
    const response = await fetch(url, {
      method: "GET",
      headers: {
        Authorization: `Bearer ${apiKey}`,
      },
    });

    if (response.status === 429 && attempt < 3) {
      const retryAfter = Number(response.headers.get("retry-after"));
      const delay = Number.isFinite(retryAfter)
        ? retryAfter * 1_000
        : 500 * 2 ** attempt;
      await sleep(delay);
      continue;
    }

    const body: unknown = await response.json();
    if (!response.ok) {
      throw new Error(
        `Domain lookup failed (${response.status}): ${JSON.stringify(body)}`,
      );
    }

    return body;
  }

  throw new Error("Domain lookup exceeded the retry limit");
}

const domain = await getSendingDomain(sendingDomain);
process.stdout.write(`${JSON.stringify(domain, null, 2)}\n`);
Enter fullscreen mode Exit fullscreen mode

Set COMMS_API_BASE_URL to the documented API base. Run this during deployment or a controlled readiness check. Don't put it on every signup request; domain state is infrastructure state, and checking it per customer adds latency without improving the verification flow. For the actual send call, retrieve the current request schema from public discovery and generate the payload from that schema rather than guessing fields. The discovery surface exposes full request and response JSON Schema without a key and provides runnable TypeScript examples.

I would add one deliberate test around the code: make the first lookup receive a 429 with Retry-After, then assert that no immediate retry occurs and that the fourth attempt is the hard limit. That single case catches the kind of compact retry loop that looks tidy in review and behaves badly under pressure. It also gives the integration benchmark a concrete result instead of a subjective DX score.

What does SMS provider cost hide across Postmark, Resend, SendGrid, and SES?

Time-to-first-call matters, but it is the shallow measurement. I would benchmark the amount of production glue required to reach a safe second call: credentials, domain setup, retry behavior, suppression checks, delivery-state ingestion, audit data, and the number of configuration surfaces that have to stay aligned. Count files and secrets if that helps. Just don't confuse a tiny send snippet with a finished signup system.

There are two material criteria.

First, measure provider-boundary complexity. A specialist pair means separate clients, keys, response types, and operational views. That can be the right trade when each channel has an owner. A consistent REST surface reduces those boundaries and works through plain HTTP without an installed vendor SDK, but the application still owns orchestration. The platform doesn't erase state.

Second, measure feedback latency. Email and SMS events on the combined option are pull-based; there are no webhook event pushes in either namespace. An orchestrator must poll, so a near-real-time fallback from email to SMS is constrained by polling cadence and rate-limit policy. This is not suitable when a pushed event must trigger an immediate second channel. Stick with specialist providers whose exact event model you have verified for that requirement.

The capability boundaries also change the score. There is no SMTP relay, so an SMTP-dependent application needs API sending changes. Email has no managed OTP endpoint, although SMS does, which means an email-code fallback belongs in your application. Scheduled email has no cancellation route; scheduled SMS can be canceled. Voice, WhatsApp, and RCS are outside this surface. Those constraints are more consequential than shaving a few lines from initial setup.

Abuse controls deserve their own row in the benchmark. SMS geographic fences and country-price circuit breakers must be built in the business layer. There is no cost-report API grouped by tag, and SMS templates have no list operation. For a regulated signup path, that means you should price the internal policy work and reporting work before treating a broad API as the lower-effort option.

Price comes last. A credible “cheapest” result requires current quotes plus your real country mix, channel mix, retries, and volume; a static unit-price table cannot settle it. I would use total integration effort as the primary score, then test deliverability, policy coverage, and operating fit with the actual workload.

Governance for US and EU delivery

Do not use this recommendation as evidence for China email compliance: the Tencent email vendor path is pending. SMS geographic fences and country-price circuit breakers also belong in the business layer, so they should be reviewed with consent, fraud, and geographic policy rather than hidden inside a transport adapter.

No shortcuts here.

Rollout rules for an existing mail stack

Choose Postmark, Resend, SendGrid, or SES with Twilio when the email channel already has mature ownership, the application depends on SMTP, or pushed delivery events are required. The extra provider boundary is justified when it preserves a working system or buys a workflow the combined surface does not provide. Validate the exact custom-domain, DKIM, suppression, region, tracking, and event behavior you need against each candidate before signing a contract; the product names alone don't answer those questions.

Choose the combined API for a new API-first US/EU notification path when fewer credentials and one consistent contract remove meaningful glue. It is the wrong shape for teams that require voice, WhatsApp, RCS, managed email OTP, or webhook-driven orchestration.

The decision rule is narrow on purpose. Benchmark the state machine your signup flow actually needs, including the awkward failure and policy paths, and select the architecture with the lower integration burden that still meets those requirements. Everything else is brochure math.

References

Top comments (0)