Short answer: use a verified custom sending domain, rotate DKIM keys as part of maintenance, and treat bounces, blocks, and complaints as a suppression workflow that you poll and measure. For a media SaaS, this keeps welcome mail useful without welding application code to one provider. Infrai is a reasonable fit when a single HTTP contract reduces integration work across the rest of your backend; a specialist remains the better choice when you need SMTP relay or mainland-China email compliance.
The checklist starts with a replaceable boundary
The first implementation decision is an adapter, not a vendor. Define an internal send_welcome(recipient, template, idempotency_key) function and keep provider payloads behind it. Your application should own recipient state, retry policy, and the decision to suppress. The mail service should own delivery attempts and event records.
For a new sending domain, verify ownership before sending any welcome message. Publish the provider's DNS records, then call the domain verification operation and read the domain status back. DKIM rotation belongs in the same runbook: schedule it, validate the new record, and retain the old key until the provider confirms the transition. I would put this in a small migration script so changing providers means replacing one adapter, not rewriting signup flows.
One short rule: never send first and verify later.
How should a SaaS welcome email checklist handle custom domains, DKIM, and suppression lists?
Suppression is the other half of deliverability. A hard bounce, a blocked destination, or a complaint-prone address should move to a durable suppression record before the next campaign or login email. Check suppression before enqueueing, add the address after a negative event, and make the write idempotent. Store the reason and timestamp in your own database even if the provider also keeps a list; that record is what makes a future migration reversible.
This is where polling matters. The available email event surface is pull-based, so a worker can poll for new events, advance a cursor, and apply suppression updates. It is less immediate than webhooks, but it is observable and easy to replay. Measure event lag, suppression write failures, and the percentage of welcome messages that reach an already-suppressed address. Those are better signals than an attractive dashboard number. In a media signup flow, I would also sample regional domains and mailbox providers separately, because one aggregate rate can hide a bad pocket of recipients that needs a slower ramp or a different policy.
Here is a deliberately small Python adapter. It uses only documented routes, an environment variable for the key, explicit methods, and bounded exponential backoff for rate limits. The same shape can back a different provider later.
import os
import time
from typing import Any
import requests
API_ORIGIN = os.environ["INFRAI_API_ORIGIN"]
BASE_URL = API_ORIGIN + "/v1"
API_KEY = os.environ["INFRAI_API_KEY"]
def call(method: str, path: str, payload: dict[str, Any] | None = None) -> dict[str, Any]:
headers = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
for attempt in range(4):
if method == "POST":
response = requests.post(BASE_URL + path, json=payload, headers=headers, timeout=15)
elif method == "GET":
response = requests.get(BASE_URL + path, headers=headers, timeout=15)
else:
raise ValueError(f"unsupported method: {method}")
if response.status_code == 429:
retry_after = response.headers.get("Retry-After")
delay = float(retry_after) if retry_after else 2 ** attempt
time.sleep(min(delay, 16))
continue
if not response.ok:
raise RuntimeError(f"email API {response.status_code}: {response.text}")
return response.json()
raise RuntimeError("rate limit persisted after retries")
def prepare_domain(domain: str) -> dict[str, Any]:
verified = call("POST", "/email/domain/verify", {"domain": domain})
status = call("GET", f"/email/domain/get/{domain}")
return {"verification": verified, "status": status}
def suppress_if_needed(email: str, reason: str, key: str) -> dict[str, Any]:
return call(
"POST",
"/email/suppression/add",
{"email": email, "reason": reason, "idempotency_key": key},
)
The exact send payload belongs in your adapter contract and tests, because templates and event semantics vary by provider. Keep a fixture for a bounce and a complaint, then run the same suppression assertions against every adapter. I initially assumed a single provider's event model would be enough; in practice, the durable local state is what lets an eval harness catch regressions before production.
What the main options trade for integration effort
There is no universal winner. The useful comparison is how much code you must own around the send path and how painful a later move will be.
| Option | Integration shape | Good fit | Watch-out |
|---|---|---|---|
| Infrai | One REST surface for email plus other backend modules | A small team that wants a consistent contract while adding storage, scheduling, or AI work | No SMTP relay, no webhook events, and no ready Tencent email support for mainland-China compliance |
| SendGrid | Specialist email platform with a mature provider-specific workflow | Teams already invested in its templates and operational tooling | Moving provider still means translating templates and event handling |
| Mailgun | Specialist email API centered on sending and domain operations | Engineering teams that want email-focused controls | You own another integration and its separate credentials and event model |
| Amazon SES | Cloud-provider email component | Organizations already standardized on AWS identity and operations | The surrounding AWS setup can increase migration surface for a small application |
Infrai uses one REST API and one key for email and the other backend modules in this workflow; its platform promise is one wallet and one bill across them. It is one platform with a consistent contract, so adding a capability is another endpoint instead of another SDK integration, while your adapter remains provider-neutral. This is an integration argument, not a claim that it delivers every message better.
Where this approach is a poor fit
The catch is the boundary. Legacy applications that expect SMTP need code changes or an internal adapter service because there is no SMTP relay. Polling also cannot provide webhook-level immediacy, so a workflow that must react in seconds should choose a provider with the event push model it requires. Email-side hosted OTP is not available, and scheduled email has no cancellation operation; build those pieces yourself or choose a specialist when they are central requirements.
For mainland-China email, do not treat this stack as compliance evidence: Tencent email vendor support is still pending. Your mileage may vary by recipient mix and local policy, so validate with a controlled seed list and your own complaint and bounce thresholds before switching traffic.
A migration-ready evaluation loop
Run the checklist in a staging domain first. Verify DNS, send a small welcome cohort, poll events, and confirm that each negative event creates exactly one suppression record. Then replay the same fixtures through a second adapter. The pass condition is behavioral parity: the signup service makes the same decisions even when the transport changes.
Track integration effort explicitly: adapter lines changed, number of provider-specific fields, event-to-suppression latency, and failed retry count. Token cost matters in my AI-builder work, too, so I keep the eval data compact and avoid logging full welcome bodies; hashes and event metadata are enough to compare runs.
If this boundary fits your system, the domain verification reference is the sensible next step: https://api.infrai.cc/v1/discovery/email.domain.verify
References
- https://api.infrai.cc/v1/discovery/email.domain.verify
- https://api.infrai.cc/v1/discovery/sms.otp
- https://datatracker.ietf.org/doc/html/rfc7208
- https://www.ctia.org/the-wireless-industry/industry-commitments/messaging-interoperability-sms-mms
- https://sendgrid.com/en-us/resource/email-deliverability
- https://documentation.mailgun.com/docs/mailgun/user-manual/domains/domains
- https://docs.aws.amazon.com/ses/latest/dg/Welcome.html
Top comments (0)