Features
An email API that's easy to build on and safe to test
One REST API with SDKs in five languages, signed webhooks, test keys that deliver nothing, and simulator addresses for every bounce and complaint your code needs to handle.
Sending
The calls a real app needs
Everything below works the same from the REST API, every SDK and the CLI.
Batch send
Scheduled send
Idempotency keys
Attachments and inline images
Templates by alias
Errors with a fix
Batch send
curl -X POST https://api.groundmail.dev/emails/batch \
-H "Authorization: Bearer $GROUNDMAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"emails": [
{ "from": "receipts@yourdomain.com", "to": "ada@example.com",
"subject": "Your receipt", "html": "<p>Thanks, Ada.</p>" },
{ "from": "receipts@yourdomain.com", "to": "lin@example.com",
"subject": "Your receipt", "html": "<p>Thanks, Lin.</p>" }
]
}'Schedule, then cancel
# Send tomorrow morning, retry-safe
curl -X POST https://api.groundmail.dev/emails \
-H "Authorization: Bearer $GROUNDMAIL_API_KEY" \
-H "Idempotency-Key: trial-ending-8841" \
-H "Content-Type: application/json" \
-d '{
"from": "billing@yourdomain.com",
"to": "ada@example.com",
"subject": "Your trial ends tomorrow",
"html": "<p>…</p>",
"scheduled_at": "2026-11-02T09:00:00Z"
}'
# Changed your mind? Cancel it.
curl -X PATCH https://api.groundmail.dev/emails/msg_abc123 \
-H "Authorization: Bearer $GROUNDMAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "cancel": true }'Test mode
Test keys and simulator addresses
A test key (gm_test_…) runs the whole pipeline, fires the same webhooks a real delivery would, and delivers nothing. Test sends don't count toward your plan, the sending ramp, your stats or your bill.
Send to these addresses at simulator.groundmail.dev for a specific outcome, with real webhook payloads and signatures. Plus addressing works, so bounce+signup-42@ tells your tests apart.
- delivered@
- Delivered
- bounce@
- Hard bounce: the mailbox doesn't exist
- block@
- Block bounce: the receiving server refused the sender
- complaint@
- Delivered, then marked as spam
- suppressed@
- Treated as a suppressed recipient
- delayed@
- The receiving server deferred delivery
Test a bounce
import { Groundmail, simulatorAddress } from 'groundmail'
// A gm_test_ key runs the whole pipeline but delivers nothing
const groundmail = new Groundmail(process.env.GROUNDMAIL_TEST_KEY!)
await groundmail.emails.send({
from: 'you@yourdomain.com',
to: simulatorAddress('bounce', 'signup-42'),
subject: 'Bounce handling test',
html: '<p>Simulated.</p>',
})
// → your webhook gets email.bounced with "test": trueDetails in the testing docs.
Webhooks
Signed events for everything that happens
Subscribe an endpoint to the events you care about. Every event is signed with your endpoint's secret, and the Node SDK checks the signature for you. Fire a test event at any endpoint with POST /emails/test.
- email.sent
- email.delivered
- email.delivery_delayed
- email.held
- email.bounced
- email.complained
- email.opened
- email.clicked
- email.failed
- email.suppressed
- email.scheduled
- email.received
- contact.unsubscribed
- automation.started
- automation.step_completed
- automation.completed
- automation.failed
Access and data
Keys you can scope, data you can take with you
Scoped API keys
Domain-restricted keys
CSV export
SDKs
Official clients for five languages
Each wraps the same REST API, so anything an SDK doesn't cover is one HTTP call away.
- Node.js / TypeScript
- npm install groundmail
- Python
- pip install groundmail
- Go
- go get github.com/Groundmail/groundmail-sdks/go
- PHP
- composer require groundmail/groundmail
- Laravel
- composer require groundmail/laravel
- Symfony Mailer
- composer require groundmail/symfony-mailer
- Ruby
- gem install groundmail
Framework packages
Drop-in packages on npm
- Better Auth
- groundmail-better-auth
- Auth.js
- groundmail-authjs
- Clerk
- groundmail-clerk
- Payload CMS
- groundmail-payload
- Nodemailer
- groundmail-nodemailer
- NestJS
- nestjs-groundmail
- AdonisJS
- adonisjs-groundmail
- Strapi
- strapi-provider-email-groundmail
- Medusa
- medusa-notification-groundmail
- n8n
- n8n-nodes-groundmail
More in integrations.
Tooling
CLI, MCP and OpenAPI
CLI
MCP server
OpenAPI
FAQ
Common questions
How do I test without sending real email?
Create a test key (it starts with gm_test_). The API works exactly as with a live key, including templates, idempotency, scheduling, batch and webhooks, but nothing is delivered and nothing counts toward your limits or bill. To see a bounce or complaint, send to an address at simulator.groundmail.dev; those work with live keys too.
How many emails can one batch request send?
Up to 100 per call to POST /emails/batch. Each email gets its own result, so one bad item doesn't fail the rest.
How long is an idempotency key remembered?
24 hours, per account. Sending the same key with the same body returns the original result; the same key with a different body returns 409 idempotency_conflict.
Can I limit what an API key can do?
Yes. Pick scopes such as emails:send, emails:read, domains:write or webhooks:write, or the Send only and Read only presets, and restrict a key to the domains it may send from.
Which languages have an SDK?
Node.js and TypeScript, Python, Go, PHP (with Laravel and Symfony Mailer packages) and Ruby. They all wrap the same REST API, so any other language can send over HTTP or SMTP.
Put your transactional email on solid ground.
Start free on our transactional pool. Move to dedicated IPs or a dedicated server when you need the isolation. Same API either way.