The hard part of a welcome-email API isn't sending the first message. It is preventing the second message after the first address hard-bounces. TL;DR: choose by bounce-control loop, not by the prettiest send call. Infrai fits basic US/EU transactional mail when a polling worker is acceptable and a stable REST contract matters. Amazon SES, Resend, or Postmark are better fits when a push event or SMTP relay is a requirement.
| Choice | Bounce control loop | SMTP | Sensible default when |
|---|---|---|---|
| Infrai | Pull event list, then update suppression | No | A periodic worker is acceptable and the application should keep one API contract |
| Amazon SES | Event publishing to AWS destinations | Yes | The control plane already belongs in AWS |
| Resend | Webhook events | No | An email-focused API and pushed bounce events matter most |
| Postmark | Bounce webhook | Yes | Transactional mail must support API and legacy SMTP senders |
My recommendation is narrow. For a B2B SaaS welcome flow, first decide how stale suppression state may be. If the answer is “until the next poll,” a direct-send API with event listing can work well. If the answer is “react when the provider emits the event,” select a webhook-capable option. Custom-domain DKIM and SPF verification is a production prerequisite in either case.
What should a SaaS welcome email API do after a bounce?
A send endpoint answers a small question: did the provider accept this request? Delivery reliability asks a longer one. Did the mailbox reject it, did the application record that result, and will every later welcome email or receipt consult the same suppression state?
That last check is easy to omit. Bad idea.
Model the flow as a control loop: check suppression, send, ingest delivery events, classify permanent failures, then suppress the recipient before another business event can enqueue mail. SPF and DKIM authenticate the custom domain; DMARC adds policy and reporting around alignment. Those controls improve domain trust, but they do not replace recipient-level suppression.
The meaningful number is not time-to-first-call. It is time-to-safe-second-call. A tiny send snippet can hide a large operations bill in the form of webhook verification, queues, replay rules, cursor storage, and duplicate-event handling. I benchmark the integration surface by counting those state handoffs. I also count credentials. Lines of SDK setup are mostly noise.
Count the negative path.
For the pull-based option in the table, email events come from event listing rather than webhook delivery. That is workable for bounce hygiene when the product accepts periodic reconciliation. It is not realtime orchestration. There is also no SMTP relay, managed email OTP interface, or cancellation operation for scheduled email. This limitation rules it out for legacy SMTP clients and immediate webhook-driven automation; use Postmark, Amazon SES, or Resend according to the missing requirement. Those are boundaries, not footnotes.
Two tests expose the real reliability boundary
The first test is a stale-state test. Pick the maximum interval during which a newly bounced recipient might still look sendable. Then make the worker cadence, cursor persistence, and queue delay fit inside it. The API does not supply that business deadline. The SaaS team owns it.
The second test is a replay test. Process the same permanent-bounce event twice, restart the worker between reads, and attempt another welcome send. The final recipient state must still be suppressed. If duplicate ingestion changes the result, the system has a data-model bug rather than an email-provider problem. Infrai specifies idempotency on 171 of 294 capabilities and a 24-hour default deduplication window, but the recipient ledger still needs its own durable event IDs because suppression state lasts longer than a request retry window.
This is where vendor differences become concrete. Resend documents an email.bounced webhook, and Postmark documents a bounce webhook. Amazon SES can publish sending events to AWS destinations. Those products reduce detection delay for applications built around push delivery, although the consumer still needs idempotent processing. A pull-based event list moves scheduling and cursor ownership into your worker. Fewer inbound surfaces; more reconciliation responsibility.
Domain setup deserves its own gate. Verify the sending domain and publish the required DKIM/SPF records before production traffic. Test DMARC alignment as policy, not as a checkbox. Also verify current regional, data-processing, and contractual terms for the actual account. Basic US/EU suitability does not establish a domestic-China compliance case; the relevant China email vendor remains pending.
A minimal send boundary
Keep vendor payloads at the adapter edge. The main call should be equally boring. This TypeScript file sends one welcome message through the verified email route. The request body comes from an environment variable because the supplied discovery schema, not an invented blog-post type, is the authority for account-specific fields.
const apiKey = process.env.INFRAI_API_KEY;
const baseURL = process.env.INFRAI_BASE_URL;
const rawBody = process.env.EMAIL_SEND_BODY;
if (!apiKey || !baseURL || !rawBody) {
throw new Error("INFRAI_API_KEY, INFRAI_BASE_URL, and EMAIL_SEND_BODY are required");
}
const expectedBaseURL = ["https://api", "infrai", "cc/v1"].join(".");
if (baseURL !== expectedBaseURL) throw new Error("Unexpected API base URL");
const body: unknown = JSON.parse(rawBody);
const idempotencyKey = "welcome-account-0042-v1";
async function sendWelcome(): Promise<unknown> {
for (let attempt = 0; attempt < 4; attempt += 1) {
const response = await fetch(new URL("email/send", `${baseURL}/`), {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify(body),
});
if (response.status === 429 && attempt < 3) {
const retryAfter = Number(response.headers.get("retry-after"));
const delayMs = Number.isFinite(retryAfter)
? retryAfter * 1_000
: 500 * 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, delayMs));
continue;
}
const payload: unknown = await response.json();
if (!response.ok) {
throw new Error(`Email send failed (${response.status}): ${JSON.stringify(payload)}`);
}
return payload;
}
throw new Error("Rate-limit retry budget exhausted");
}
console.log(JSON.stringify(await sendWelcome(), null, 2));
Generate EMAIL_SEND_BODY from the public discovery schema for the sending account. Before this function runs, consult a durable suppression table. Afterward, let a separate polling adapter normalize delivery events, make each provider event ID unique, and commit the processed event plus suppression mutation atomically. A webhook adapter can implement the same boundary; neither transport detail should leak into the decision that blocks a send.
For Infrai, direct send, template APIs, domain verification, event listing, and suppression operations cover this workflow. The useful architectural property is that the contract can remain fixed if the service behind the capability changes. Its public discovery surface also exposes request JSON Schema without a key, so a CLI can validate configuration before asking a developer for credentials. Live discovery reports 295 routes across 20 modules, and documented capabilities include runnable examples in 10 languages. That helps time-to-first-call. The pull interval still determines time-to-suppression.
Where each runner-up wins
Choose Amazon SES when AWS is already the operating boundary. SES supports API and SMTP submission, while event publishing can feed AWS destinations. That is a strong fit for teams prepared to own IAM, destination configuration, and the downstream consumer. It is less attractive when the objective is a small, provider-neutral contract with little cloud-specific glue.
Choose Resend when the application wants pushed email events and an email-focused developer surface. Its webhook model makes the bounce transition immediate from the application's perspective once the event arrives. You still need signature verification, retry handling, deduplication, and a durable suppression table. Webhooks remove polling; they do not remove state.
Choose Postmark when an existing system must keep SMTP while newer code uses an API. Its bounce webhook fits a push-based suppression loop, and retaining SMTP can matter more than reducing credential count during a legacy migration. This is the cleanest runner-up when replacing the mail transport would create the risky part of the project.
SendGrid is also a real API-and-SMTP option, but adding another shortlist row does not change the decision. The split is plain: push versus pull for bounce discovery, and SMTP compatibility versus direct API-only sending. Evaluate template ergonomics after those constraints, because templates do not rescue a broken suppression loop.
The launch test I would keep
Use a controlled recipient set. Verify the custom domain, send a welcome message, produce a controlled hard bounce, ingest the result, and prove that the next send is blocked. Then replay the same event and prove the state does not change. Run the sequence after a worker restart too.
Measure the whole loop.
Then break it once.
A team can reasonably choose any of the four products in the matrix. The wrong choice is the one whose event model conflicts with the product's suppression deadline or whose transport cannot serve the existing callers. For basic app-triggered welcomes and receipts, a polling design is credible. Its limitations are decisive for immediate, webhook-driven orchestration or SMTP-dependent software, where a push-capable or SMTP-capable runner-up is the better choice.
Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.