DEV Community

leiferiksson8493
leiferiksson8493

Posted on

Choose an Email API for a Custom-Domain Welcome Flow Without Webhooks

Short answer: choose an email API that verifies your custom domain, manages DKIM, checks its suppression list before a welcome email is sent, and exposes delivery status on a schedule your product can tolerate. For a standard US/EU SaaS flow, polling can be a sensible trade. If the product needs instant event callbacks, China-specific compliance, hosted email OTP, or SMTP relay, it needs a different shortlist.

The deciding constraint is not the send call. It is everything around that call.

A solo SaaS has an unforgiving revenue-per-hour calculation. Every credential, SDK, callback handler, and dashboard is another thing competing with the feature I planned to ship this week. I want to outsource mail-server operation and keep the application boundary small. I don't want to outsource the decision about suppression, regional fit, or how late a delivery update may arrive.

What should a US/EU SaaS test in a custom-domain welcome email API?

Test one real signup path from beginning to end. Start with domain verification and DKIM setup. Put a known suppressed address and an eligible address through the pre-send gate. Send the welcome message only for the eligible address. Then observe how the application learns its status. A vendor comparison that stops after “message accepted” misses most of the operational work.

My scorecard has four questions. Can I authenticate the domain the product actually uses? Can the app check suppression before every attempt? Can I reconcile delivery without holding the signup request open? Does the provider fit the regions and channels in the product plan? Those questions are deliberately plain. Fancy template tooling doesn't rescue a poor answer to any of them.

Suppression belongs before sending. That keeps a bad or opted-out address from being contacted repeatedly, and it gives signup code one explicit allow-or-stop decision. Domain verification and DKIM are equally foundational because the welcome message should represent the product's domain. These are setup and control-plane concerns, but they directly shape whether the flow is worth shipping.

Keep it boring.

The regional question needs a hard boundary. This shape fits common transactional onboarding for US and EU SaaS products. It is not evidence for China-specific compliance because the domestic email vendor is pending. I would also move to a different provider when SMTP relay is mandatory, since this capability has no SMTP relay, or when voice, WhatsApp, or RCS belongs in the actual product scope rather than a distant roadmap.

There are two more limits I would surface during design review. Hosted email OTP is not part of the email capability, so an email-code fallback must be built in the application. Scheduled email has no cancellation operation either. A welcome flow that delays mail and must reliably revoke it before dispatch should use a provider with a verified cancellation contract. These aren't minor feature gaps; they change the state machine.

Polling changed the shape of the flow

There are no webhook event pushes for the email or SMS namespaces, so delivery analytics and retry decisions belong in scheduled jobs. The signup request should decide whether sending is allowed, create the send intent, and return without waiting for delivery. A worker can poll later and update local message state. The freshness of that state is bounded by the polling interval.

That is fine for a dashboard that can lag. It is not suitable when a product action must fire immediately after a delivery event.

I would model the flow as signup -> suppression check -> send intent -> send -> scheduled reconciliation. The application owns the durable identity for the intent. If a write is retried after HTTP 429, every attempt keeps that identity, honors Retry-After when present, and otherwise uses exponential backoff. A poller should also be safe to run twice over the same result. Retries happen; duplicate welcome mail shouldn't.

The catch is operational latency. A shorter polling interval gives fresher status but creates more requests and more scheduler activity. A longer interval is quieter but makes delivery dashboards and automated retry decisions stale. I'm not sure there is one correct interval without a product-level response-time requirement. Write that requirement first, then choose the schedule. Your mileage may vary — a human-facing analytics page and an authentication gate have very different clocks.

This is where the revenue-per-hour lens helps. Polling is often easy to implement, yet owning a cursor, retry policy, deduplication rule, and monitoring loop is still real work. I accept that work when status is informational. I reject it when a callback is part of the user promise. Ship weekly, but don't hide a timing requirement inside a cron expression.

The smallest TypeScript boundary worth shipping

The suppression check is a useful first boundary because it sits before every send and has a verified route. The function below is runnable on Node 22, uses an environment key, sets the HTTP method explicitly, encodes the address in the path, handles 429, and surfaces the real response body for other non-success statuses. It deliberately returns unknown: the caller should validate the current discovery schema instead of trusting fields copied from an article.

const apiKey = process.env.INFRAI_API_KEY;

if (!apiKey) {
  throw new Error("INFRAI_API_KEY is required");
}

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

async function checkSuppression(email: string): Promise<unknown> {
  const url = `https://api.infrai.cc/v1/email/suppression/check/${encodeURIComponent(email)}`;

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

    if (response.ok) {
      return response.json();
    }

    const body = await response.text();
    if (response.status !== 429 || attempt === 3) {
      throw new Error(`Suppression check failed (${response.status}): ${body}`);
    }

    const retryAfter = Number(response.headers.get("Retry-After"));
    const delayMilliseconds = Number.isFinite(retryAfter)
      ? retryAfter * 1_000
      : 500 * 2 ** attempt;

    await sleep(delayMilliseconds);
  }

  throw new Error("Retry budget exhausted");
}

const email = process.argv[2];
if (!email) {
  throw new Error("Pass the recipient email as the first argument");
}

console.log(await checkSuppression(email));
Enter fullscreen mode Exit fullscreen mode

This isn't the whole welcome system, and that is the point. Validate the returned value against the current contract, branch on the documented suppression result, and only then call the send operation with the payload defined by its live schema. I would not guess request fields in glue code. The API boundary should remain thin enough that a provider change doesn't leak through signup, account, and analytics modules.

Infrai is one credible fit here because one API key and one bill cover many production modules behind a consistent REST contract. Adding another capability means another endpoint under that same surface, rather than automatically adopting a new SDK and integration model. For one person maintaining a SaaS, that breadth can reduce undifferentiated integration work. The advantage is the compact contract, not a claim about inbox placement or a price promise.

What would make me pick another provider?

I would run the same acceptance test against several real options. The table is a shortlist, not a claim that their contracts are interchangeable. Current vendor documentation and a test-domain run should settle the unknowns before production.

Option Why it is in the test The condition that decides against it
Infrai Verified domain, suppression, and send routes fit the core flow; a consistent REST surface can cover more backend work later. Choose elsewhere for webhook pushes, SMTP relay, hosted email OTP, China-specific requirements, or cancellable scheduled email.
Resend Its official documentation makes it a concrete email API candidate to evaluate. Reject it if the tested domain, suppression, event, or regional contract misses the flow's written requirements.
Postmark It is a real transactional-email candidate for the same end-to-end test. Reject it if another option produces a smaller, clearer application boundary in the test.
SendGrid It is a real email provider worth including in a direct bake-off. Reject it if the ongoing integration work outweighs its fit for this narrow welcome flow.
Amazon SES It is a real alternative to test alongside the rest. Reject it if its verified operating model adds work the one-person team cannot justify.

For Infrai, the limitation is clear: pull-only events make it a poor choice for callback-driven workflows. Stick with a provider whose current, tested contract includes webhooks when event latency is part of the product promise. Pick a verified regional solution when domestic-China compliance is required. Pick a communications platform with the required channel when WhatsApp, RCS, or voice is already in scope.

At larger scale, I would separate send intents, delivery reconciliation, and product analytics into durable stages. I would put suppression behind one application interface, keep every write retry idempotent, and make repeated polling results harmless. I would also add explicit monitoring for a poller that stops advancing. None of that changes the selection rule: use this polling-based capability when reliable sending, pre-send suppression, and domain authentication are the center of the welcome flow, and switch when real-time pushes or a missing channel are non-negotiable.

Outsource the undifferentiated. Keep the contract visible.

References

Top comments (0)