Disclosure: I have no affiliation with SMTPfast. Nobody paid for this post. I already have an SMTPfast account for one of my own domains, and everything below through the "real-time tracing" section is a live test against that account: real API calls, a real SMTP connection, real responses. The webhook signature-verification section is written from SMTPfast's published docs rather than a live-triggered webhook, and I say so again down there so it's not ambiguous which is which.
Most transactional email APIs give you a 200 and then go quiet. You find out whether the email actually landed by checking your inbox, or worse, by a support ticket. SMTPfast's whole pitch is that it doesn't do that: every send gets a visible event timeline, and it gives you two completely different ways to get an email into that pipeline depending on what kind of app you're sending from.
What SMTPfast is
SMTPfast is a transactional email API built on top of Amazon SES. That's a deliberate choice: SES already has a decade of sender-reputation infrastructure behind it, and SMTPfast's job is to wrap that in an API that doesn't feel like raw AWS. It's also intentionally Resend-compatible, so if you already have Resend integration code, pointing it at SMTPfast is close to a base-URL-and-key swap.
Sending through the REST API
Domain verification happens once, up front. Listing my account's domains:
curl -s https://smtpfa.st/api/v1/domains \
-H "Authorization: Bearer sf_live_your_api_key"
[{"domain":"yourdomain.com","status":"verified","dkimStatus":"verified","spfStatus":"verified","dmarcStatus":"verified","verifiedAt":"2026-05-25T20:15:29.784Z"}]
With a verified domain in hand, sending an email is one POST:
curl -s -X POST https://smtpfa.st/api/v1/emails \
-H "Authorization: Bearer sf_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"from": "noreply@yourdomain.com",
"to": ["you@example.com"],
"subject": "SMTPfast test",
"html": "<h1>Hello from SMTPfast!</h1>"
}'
{"id":"cmu36wssg07keut72gfoiai6l"}
That id is the whole point. Fetching GET /v1/emails/:id immediately after gave me the full event timeline for that exact send, not a generic "sent" flag:
{"id":"cmu36wssg07keut72gfoiai6l","status":"sent","source":"api","sent_at":"2026-09-15T21:34:47.018Z","ses_message_id":"010b01a0a6fe6c38-b3c22e8a-2130-4a0c-9fbf-faaa57dbcf2d-000000","events":[{"type":"queued","timestamp":"2026-09-15T21:34:46.733Z"},{"type":"sending","timestamp":"2026-09-15T21:34:46.765Z"},{"type":"sent","metadata":{"messageId":"<694f9d3f-6550-4790-b18f-d1a8dfbd1e9d@yourdomain.com>","sesMessageId":"010b01a0a6fe6c38-b3c22e8a-2130-4a0c-9fbf-faaa57dbcf2d-000000"},"timestamp":"2026-09-15T21:34:47.028Z"}]}
That's a real response from a real send, trimmed for length but not altered. The ses_message_id field is the tell that this is SES under the hood, and it's genuinely useful: if a delivery problem ever needs escalating past SMTPfast to AWS directly, you already have the ID that AWS support will ask for.
Sending through the SMTP bridge, with zero application code
This is the part that's easy to undersell. A lot of software that needs to send email was never written with a modern HTTP API in mind: a WordPress plugin, an old Rails app's ActionMailer config, some internal tool that just calls sendmail. Rewriting those to use a JSON API is real work that nobody wants to schedule.
SMTPfast's SMTP bridge means you don't have to. Point any SMTP client at it with your API key as the password, and it goes through the exact same domain verification, rate limits, and logging as the REST API:
Host: smtp.smtpfa.st
Port: 587 (STARTTLS)
Username: smtpfast
Password: sf_live_your_api_key
I tested this with plain smtplib, deliberately not SMTPfast's own SDK, to prove it really is just standard SMTP:
import smtplib
from email.mime.text import MIMEText
msg = MIMEText("This one went through the SMTP bridge, not the REST API.", "html")
msg["Subject"] = "SMTPfast SMTP bridge test"
msg["From"] = "noreply@yourdomain.com"
msg["To"] = "you@example.com"
with smtplib.SMTP("smtp.smtpfa.st", 587, timeout=20) as s:
s.starttls()
s.login("smtpfast", "sf_live_your_api_key")
s.send_message(msg)
That connected, authenticated, and sent on the first try, no SMTPfast-specific code at all. If you can configure SMTP_HOST, SMTP_PORT, SMTP_USER, and SMTP_PASS environment variables in whatever you're running, this is the entire migration.
The one thing I couldn't verify from outside the dashboard: whether the SMTP-sent message shows up in the same per-email event timeline as the API-sent one, since there's no list-emails endpoint to search by recipient or subject, only GET /v1/emails/:id by the id the REST API hands back. The docs state the SMTP bridge uses "the same verified domains, rate limits, suppression checks, queueing, logs, and webhooks as the HTTP API," which lines up with everything else I saw, but I'm flagging it as a documented claim rather than something I personally watched happen in a dashboard.
Webhooks: what's documented, not live-tested here
This part I did not trigger live. Setting up a public inspection endpoint to catch a real webhook payload from an account I'd just authenticated against got flagged by my own tooling's safety checks as a plausible data-exfiltration pattern, reasonably so, and I didn't try to route around it. So take this section as an accurate read of SMTPfast's docs, not a confirmed live test.
Creating a webhook:
curl -s -X POST https://smtpfa.st/api/v1/webhooks \
-H "Authorization: Bearer sf_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/webhooks/smtpfast",
"events": ["email.delivered", "email.bounced", "email.complained"]
}'
{"id":"wh_abc123","url":"https://yourapp.com/webhooks/smtpfast","events":["email.delivered","email.bounced","email.complained"],"signing_secret":"whsec_abc123...","active":true}
That signing_secret is what you use to verify a payload actually came from SMTPfast and not from someone who found your endpoint URL. Failed deliveries get retried on a schedule, and SMTPfast keeps a delivery log per webhook, including the exact payload and response code of every attempt, with a manual retry endpoint (POST /v1/webhooks/:id/deliveries/:delivery_id/retry) for when your endpoint was down and you don't want to wait for the next scheduled attempt. That's a level of webhook observability a lot of providers don't bother exposing, they'll retry silently and give you nothing to inspect if you missed the window.
A few ideas worth building with this
Once sending is this cheap to wire up and this easy to trace, a few things stop being "too much effort for a side project" and start being an afternoon:
-
A "did you actually read it" gate. Use the per-email event timeline to hold a UI in a waiting state until
opened_atshows up, instead of just firing a magic-link email and hoping. Useful for onboarding flows where you don't want to show "check your inbox" and move on before you know they will. -
Self-healing recipient lists. Subscribe a webhook to
email.bouncedandemail.complained, and flag or suppress that address in your own database the moment the event lands, instead of finding out three campaigns later that an address has been dead since March. -
A newsletter without an ESP. The batch endpoint takes up to 100 personalized emails in one request, each with its own
tags. Loop your subscriber list in batches of 100, tag each send with a campaign id, and you've got per-campaign open/bounce numbers without paying for a full marketing platform. -
Scheduled sends as a poor man's cron.
scheduled_aton a batch send means "remind me in 3 days" or "send this digest every Monday at 9am" doesn't need its own job queue, just a send call with a future timestamp. - Let an AI agent send the email itself. SMTPfast's MCP server means a coding agent (or any MCP-aware agent) can read the API surface and wire up "email me when the deploy fails" or "send the on-call a summary" without a human writing the integration first. Worth trying even just to see how much of the plumbing an agent gets right unassisted.
None of these are SMTPfast-exclusive; any transactional email API with webhooks, batch sending, and scheduled sends can do most of this. SMTPfast's angle is that the tracing to actually check whether it worked is built in from the start instead of something you bolt on later.
What I'd actually trust this for
Based on what I could verify directly: the REST API and the SMTP bridge both work exactly as documented, authenticate the same way, and produce a send you can track by id. That combination is the actual answer to "how do I add SMTPfast to something," whether that something is a brand-new service you're writing against the API, or a decade-old app you just want to stop worrying about.
What I haven't verified myself: webhook delivery in practice, and how the dashboard actually surfaces an SMTP-bridge send next to an API send. If you're evaluating this for something webhook-dependent, test that part yourself before you commit, the API and SMTP bridge testing here should save you that step for everything else.
Try it yourself
# Verify your domain first at https://smtpfa.st/dashboard, then:
curl -X POST https://smtpfa.st/api/v1/emails \
-H "Authorization: Bearer $SMTPFAST_API_KEY" \
-H "Content-Type: application/json" \
-d '{"from":"noreply@yourdomain.com","to":["you@example.com"],"subject":"test","html":"<p>hi</p>"}'
# and the SMTP side, from a shell with Python installed:
python3 -c "
import smtplib
from email.mime.text import MIMEText
msg = MIMEText('smtp bridge test', 'plain')
msg['Subject'] = 'test'
msg['From'] = 'noreply@yourdomain.com'
msg['To'] = 'you@example.com'
with smtplib.SMTP('smtp.smtpfa.st', 587) as s:
s.starttls()
s.login('smtpfast', 'YOUR_API_KEY')
s.send_message(msg)
"
3,000 emails/month are free, no card required, so there's no reason not to run both of these against your own inbox before deciding.