Send emails with Inngest
Install the Inngest adapter and call sendEmail inside step.run.
What you'll build
An Inngest function that sends a welcome email when a user signs up. The adapter runs the send as a step, so Inngest retries it and replays never send twice.
Before you start
- An API key with Send only access. Full access also works. See Create an API key. Support keys cannot send.
- A verified sending domain. See Add and verify a domain.
frommust be an address on it. - A cluster that is ready to send (
sending_readyistrue). See Clusters. - Your team id and cluster id. Both are on the API keys page.
- Node.js 18 or later.
Install
Install the adapter with inngest.
Send your first email
Call sendEmail with the function's step, your config, and the message. Pass idempotencyKey from event.id so a replayed step does not send twice.
Serve the function with your Inngest handler, then send an app/user.created event. sendEmail runs a step named postshiba-send and returns { queued, messageId }. The message then shows under Events in the dashboard.
clusterId is required. The adapter posts to POST /api/v1/teams/:team_id/clusters/:cluster_id/sends with an Idempotency-Key header.
Attachments and templates
Attach a file
Pass attachments with filename, contentType, and base64 content. Up to 10 files, 18 MB in total. See Attachments for allowed types.
Send a published template
Pass template with the alias or public id and its variables. Leave out html and text.
Keep the key safe
Use a Send only key in your functions. It can send email and call GET /api/v1/users/me. Every other request returns 403 forbidden.
| If this key leaks | Send only | Full access |
|---|---|---|
| Send mail from your verified domains | Yes | Yes |
| Read events, recipients, templates, and sent messages | No | Yes |
| See SMTP passwords and webhook secrets | No | Yes |
| Change clusters, domains, credentials, webhooks, and suppressions | No | Yes |
- Keep Full access keys for setup scripts, the CLI, and MCP. Don't deploy one with your functions.
- Set
POSTSHIBA_API_KEYin the environment of the app that serves your Inngest functions, and passprocess.env.POSTSHIBA_API_KEYasapiKey. Don't commit it, log it, or put it in event data. - Send from the function. Never put a key, even a Send only key, in browser or mobile code.
- Create one key per app and environment, so your Inngest production and branch environments never share one. If a key leaks, delete it on API keys and create a new one. Deleting stops it straight away.
Troubleshooting
The adapter maps API errors onto Inngest errors before they leave the step. PostShibaHttpError carries status, code, and retryable.
| Status | error |
What the adapter does |
|---|---|---|
401 |
Invalid token or no user signed in |
Throws NonRetriableError. Check the API key. |
403 |
forbidden, domain_unverified, or kyc_required |
Throws NonRetriableError. Support keys cannot send, so use a Send only key and a verified from. While your team is pending production approval, send only to team members, live inbox addresses, and inbox forward-to addresses. |
409 |
idempotency_conflict |
Throws NonRetriableError. The same idempotencyKey was used with a different message. |
422 |
invalid |
Throws NonRetriableError. code and the error message say which field. |
429 |
throttled |
Throws RetryAfterError, and Inngest waits one hour before the next attempt. Do not send the same message from another system in that window. See What happens at your cap. |
Every other status is on Errors.
Next steps
- Verify webhook requests, or use the adapter's
handleWebhookto turn delivery events intopostshiba/email.eventInngest events. - Unique args to get your own ids back on delivery webhooks.
- Send emails with Node.js for the plain client.
- SDKs for every language client.