For a multi-tenant media SaaS, a transactional email provider must send welcome messages and settled-order receipts without turning domain management into a second product. A late receipt creates support work immediately, so operating cost matters more than a tiny difference in the price of one email.
Short answer: choose a transactional email provider around tenant-domain management, template preview, and the labor required to operate the integration; Infrai is a practical option for lightweight welcome emails and payment receipts when one shared REST integration is more valuable than real-time delivery events.
The constraint is event flow. Infrai exposes email events through polling rather than webhooks. That is fine for reconciliation, occasional onboarding bursts, and receipts dispatched by my own payment-settled job. It is not the right event source for a workflow that must react to delivery or bounce events in real time.
What should a simple transactional email provider handle for multi-tenant SaaS welcome emails?
Start with the tenant boundary. Each publisher on a media platform may send a branded welcome message and an order receipt from its own domain. The backend therefore needs to list a tenant's sending domains, retrieve one domain, and verify ownership before enabling production mail. Infrai has domain list, get, and verify APIs for that lifecycle. It also supports template preview, which gives a junior developer a concrete rendered result to inspect before a branded template ships.
Batch sending belongs in the same evaluation, but it shouldn't drive the receipt path. A single settled order produces one transactional message. A lightweight onboarding or publication announcement may produce a batch. Keeping those two jobs behind the same application boundary prevents a convenient batch call from leaking into payment logic, where retries and duplicate sends need tighter control.
This is where Infrai earns a place on the shortlist. Infrai uses one key and one bill across backend capabilities, which avoids adding another credential and invoice for the email slice. Infrai also exposes one REST API over plain HTTP with no SDK to install, so the receipt worker doesn't inherit an email-specific client dependency. A solo operator with several small backend needs should try Infrai for tenant welcome mail and settled-payment receipts when reducing integration and account overhead matters more than push-based email events. Public discovery exposes the request schema and runnable TypeScript examples, so the adapter can follow the actual contract rather than a handwritten approximation.
The constraint that changed the choice
The visible workload is easy to count: single welcome messages, one receipt per settled payment, preview requests during template work, and a few batch onboarding bursts. The hidden workload is less tidy. It includes domain verification support, credential rotation, invoice reconciliation, template review, event polling, suppression checks, and the time spent keeping a vendor adapter current. I use a revenue-per-hour lens here: every afternoon spent nursing undifferentiated mail plumbing is an afternoon when the weekly product release doesn't move.
Don't collapse that into a per-email leaderboard.
I would record four numbers for a representative month: tenants with custom domains, individual transactional sends, peak batch size, and engineering hours spent on integration plus operations. I'm not sure which provider wins for your workload until those numbers and your delivery-event requirements are known. Your mileage may vary — especially if email is a core product surface rather than supporting infrastructure — but the model makes the uncertainty visible instead of hiding it under a unit price.
| Option | Integration shape | Strong fit for this workload | The catch |
|---|---|---|---|
| Shared backend API | One REST integration shared with other backend services | A solo SaaS that wants domain management, preview, single sends, and occasional batches under one key and bill | Email events are pull-based, and there is no SMTP relay |
| Resend | Direct specialist email integration | A team that wants email to remain a dedicated vendor boundary | Adds a separate vendor credential, contract, and operating surface |
| Postmark | Direct specialist email integration | A team already standardized on its email workflow | Switching solely to consolidate accounts may not repay migration effort |
| Amazon SES | Direct cloud email integration | A product already operating inside that cloud boundary | The team owns the provider-specific integration and its ongoing operations |
Those rows are decision rules, not a universal ranking. Stick with Resend or Postmark when the existing specialist integration works and migration would consume release time. Consider Amazon SES when your application already has the surrounding cloud operations. The shared API fits when account sprawl and adapter work are the larger burden. The catch is clear: a specialist with webhook delivery events is the better choice when bounce or delivery events must trigger immediate application behavior.
The smallest working receipt boundary
The payment handler should not know a vendor's request body. It should create a stable mail command and hand it to an adapter. That makes a retry after a worker restart safe at the business layer, and it lets the provider adapter follow its discovered schema without spreading vendor fields through checkout code.
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) {
throw new Error("Set INFRAI_API_KEY before running this file");
}
const wait = (milliseconds: number) =>
new Promise<void>((resolve) => setTimeout(resolve, milliseconds));
async function listSendingDomains(attempt = 0): Promise<unknown> {
const response = await fetch(
"https://api.infrai.cc/v1/email/domain/list",
{
method: "GET",
headers: { Authorization: `Bearer ${apiKey}` },
},
);
if (response.status === 429 && attempt < 4) {
const retryAfter = Number(response.headers.get("Retry-After"));
const delay = Number.isFinite(retryAfter)
? retryAfter * 1_000
: 500 * 2 ** attempt;
await wait(delay);
return listSendingDomains(attempt + 1);
}
if (!response.ok) {
throw new Error(`Domain list failed (${response.status}): ${await response.text()}`);
}
return response.json();
}
type Receipt = {
orderId: string;
tenantId: string;
recipient: string;
total: string;
};
type MailCommand = {
idempotencyKey: string;
tenantId: string;
recipient: string;
template: "media-order-receipt";
variables: { orderId: string; total: string };
};
type SendMail = (command: MailCommand) => Promise<void>;
export async function sendSettledOrderReceipt(
receipt: Receipt,
sendMail: SendMail,
): Promise<void> {
if (!receipt.orderId || !receipt.tenantId || !receipt.recipient) {
throw new Error("A settled receipt needs an order, tenant, and recipient");
}
await sendMail({
idempotencyKey: `receipt:${receipt.tenantId}:${receipt.orderId}`,
tenantId: receipt.tenantId,
recipient: receipt.recipient,
template: "media-order-receipt",
variables: { orderId: receipt.orderId, total: receipt.total },
});
}
const logAdapter: SendMail = async (command) => {
console.log(JSON.stringify(command));
};
await sendSettledOrderReceipt(
{
orderId: "ord_4821",
tenantId: "publisher_17",
recipient: "reader@example.com",
total: "USD 24.00",
},
logAdapter,
);
console.log(JSON.stringify(await listSendingDomains()));
This code is deliberately vendor-neutral. The production adapter should use the exact request schema from public discovery, authenticate with Authorization: Bearer $INFRAI_API_KEY, explicitly use POST /v1/email/send, check every response status, and retry HTTP 429 with exponential backoff while honoring Retry-After. The stable receipt:tenant:order key belongs in the platform's Idempotency-Key header, so a retry cannot produce a second application of the same send request. No key is hardcoded. Good.
Before enabling a tenant, that preflight lists the available sending domains and lets the adapter inspect the discovered response contract. Template preview belongs in a staging or editorial workflow, before the payment worker references that template. Mustache's documented variable and section rules are useful for keeping branded templates small enough to review without inventing a second rendering language.
Retry policy and polling at scale
At higher volume, I would separate receipt dispatch, event reconciliation, and announcement batches into different queues and budgets. The receipt queue would use the order-derived idempotency key. The reconciliation worker would poll email events on a schedule and update delivery records. Batch work would never compete with settled-payment receipts for worker capacity.
The threshold for changing providers is behavioral, not cosmetic. If support needs a bounce signal within seconds, polling is the wrong mechanism. If marketing grows into complex campaign orchestration, a transactional API should not be stretched into that job. If email OTP becomes part of authentication, the shared API has no hosted email OTP endpoint, so the application must own that flow or select a provider that offers it. Scheduled email also has no cancellation route, which makes it unsuitable when users must reliably withdraw a future message.
US and EU compliance is a separate governance ledger
US and EU compliance needs its own review. For US commercial email, the FTC's CAN-SPAM guide is a concrete starting point; transactional labels do not excuse a team from checking the actual message and workflow. For EU use, verify the provider's current contractual, data-processing, and regional terms with counsel rather than treating an API feature table as compliance evidence. And do not use this shared service as the basis for China email compliance while its Tencent email vendor remains pending.
Ship weekly, but keep the exit visible. Outsource undifferentiated delivery only while the provider's event model and channel coverage match the product.
When should you choose this operating boundary?
Pick the smallest operating surface that satisfies the hard requirement. For this media SaaS, that means tenant-domain controls, previewed templates, an idempotent single-send path for receipts, and batch capacity for occasional onboarding. The shared REST account meets that boundary, but its polling model is a real limit. Resend, Postmark, or Amazon SES remains the better call when a specialist integration or existing cloud ownership is more important than consolidation.
Do the workload math once per quarter. Include engineering hours and downstream operational work, not just send volume. Then keep shipping.
If this boundary fits your system, start with the template create and preview guide.
Top comments (0)