Keep every lead with a reliable form backend that writes to Postgres first
FormThunder saves each form submission to Postgres before it returns success, and it writes the delivery jobs in the same transaction, so a crash can't lose one. Email, webhook and chat deliveries then retry with exponential backoff for up to 24 hours. You can see every attempt, and resend any that failed.
Updated
What happens to a submission
1. The post arrives
A load balancer sends it to one of at least two app containers, in two AWS availability zones.
2. It's written to Postgres
The submission and one delivery job per channel go in a single transaction. If that write fails, we return a 503 with Retry-After, and we don't claim success.
3. We reply success
Only after the commit. From here a crash can't lose it, because the work to do is stored with the data.
4. A worker sends it
Workers in every container claim jobs with SKIP LOCKED, so any number can run at once. If one dies holding a job, that job becomes available again.
5. Failures retry
Each channel retries on its own, for up to 24 hours. A Slack outage doesn't hold up your email.
6. You can see all of it
Each delivery shows status, attempts and last error. After 10 attempts it's marked failed, and you can resend it.
Retries, in full
10 attempts over about 24 hours. Most outages are short, so early retries catch them, and the later ones cover the long ones without hammering a service that's struggling.
| Attempt | Waits before it |
|---|---|
| 1 | Right away |
| 2 | 30 seconds |
| 3 | 2 minutes |
| 4 | 10 minutes |
| 5 | 30 minutes |
| 6 | 1 hour |
| 7 | 2 hours |
| 8 | 4 hours |
| 9 | 8 hours |
| 10 | 8 hours |
Email fails over to a second provider
We send through Amazon SES first. If SES fails with a temporary error, we try Resend. A single provider's bad day becomes a line in the delivery's log.
The infrastructure
It's deliberately boring. Everything runs on AWS, in one image, with the web server and background worker in the same process.
- At least two app containers on ECS Fargate, across two availability zones, behind a load balancer.
- Amazon RDS Postgres in Multi-AZ, with automated backups.
- S3 for uploaded files, and Secrets Manager for credentials.
- CloudWatch alarms watching it all.
- Migrations run at boot under an advisory lock, so deploys can't run them twice.
Webhooks you can trust
Every form webhook is signed with HMAC-SHA256 over the timestamp and the raw body, so your endpoint can check that it came from us and that it's recent.
X-FormThunder-Signature: t=1793957400,v1=5f8a0c...See webhook verification code, or read why form submissions get lost.
Questions
Do you publish an uptime number?
No. FormThunder has only just launched, so an uptime percentage wouldn't mean anything yet. This page describes what the system does. Each deploy's health endpoint is at /api/health.
What happens if a provider rejects an email permanently?
Failover and retries are for temporary errors. A permanent rejection, such as a bad address, marks the delivery failed straight away, with the reason shown, because retrying wouldn't help.
What happens to submissions over my monthly limit?
They're stored and held, and released when the account upgrades. You get an email at 80% of your limit and when you reach it.
Is delivery exactly once?
No. Delivery is at least once: if a worker sends a webhook and dies before recording that, the webhook goes out again. The X-FormThunder-Delivery header stays the same across retries, so your endpoint can ignore duplicates.
Are backups automatic?
Yes. Postgres runs Multi-AZ on Amazon RDS, with automated backups.
Send a test and watch the delivery log
The free plan covers 250 submissions a month, with MCP, the CLI and the brand kit included.