Sign in to fill these examples with your own keys. Sign in Start free trial

Send emails with Trigger.dev

Install the Trigger.dev adapter and call sendEmail from a task.

What you'll build

A Trigger.dev task that sends a welcome email. The adapter tells Trigger.dev which errors to retry, and when.

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. from must be an address on it.
  • A cluster that is ready to send (sending_ready is true). 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 @trigger.dev/sdk.

install.sh
1npm install @postshiba/trigger @trigger.dev/sdk

Send your first email

Define a task that calls sendEmail. Use defaultRetry and put catchPostShibaError on catchError. Pass a stable idempotencyKey so a retried run does not send twice.

send.ts
1import { task } from "@trigger.dev/sdk" 2import { catchPostShibaError, defaultRetry, sendEmail } from "@postshiba/trigger" 3 4export const sendWelcome = task({ 5 id: "send-welcome", 6 retry: defaultRetry, 7 catchError: async ({ error }) => catchPostShibaError(error), 8 run: async (payload: { to: string; id: string }) => { 9 return await sendEmail( 10 { 11 apiKey: "YOUR_API_KEY", 12 teamId: "KjkAJW", 13 clusterId: "NmQpXr", 14 }, 15 { 16 from: "hello@mail.example.com", 17 to: [payload.to], 18 subject: "Welcome", 19 text: "Glad you are here.", 20 idempotencyKey: payload.id, 21 }, 22 ) 23 }, 24})

Trigger the task with { to: "you@example.com", id: "user_123" }. sendEmail 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.

attachment.ts
1await sendEmail(config, { 2 from: "hello@mail.example.com", 3 to: [payload.to], 4 subject: "Your photo", 5 text: "See attached.", 6 attachments: [ 7 { 8 filename: "photo.png", 9 contentType: "image/png", 10 content: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==", 11 }, 12 ], 13 idempotencyKey: payload.id, 14})

Send a published template

Pass template with the alias or public id and its variables. Leave out html and text.

template.ts
1await sendEmail(config, { 2 from: "hello@mail.example.com", 3 to: [payload.to], 4 template: { id: "welcome", variables: { name: payload.name } }, 5 idempotencyKey: payload.id, 6})

Keep the key safe

Use a Send only key in your tasks. 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 tasks.
  • Add POSTSHIBA_API_KEY to your Trigger.dev project's environment variables and pass process.env.POSTSHIBA_API_KEY as apiKey. Don't commit it, log it, or put it in a task payload.
  • Send from the task. Never put a key, even a Send only key, in browser or mobile code.
  • Create one key per Trigger.dev environment, so Development and Production never share one. If a key leaks, delete it on API keys and create a new one. Deleting stops it straight away.

Troubleshooting

A response outside 2xx throws PostShibaHttpError with status, code, and retryable. Permanent errors become AbortTaskRunError.

Status error What the adapter does
401 Invalid token or no user signed in Stops the run without retrying. Check the API key.
403 forbidden, domain_unverified, or kyc_required Stops the run. 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 Stops the run. The same idempotencyKey was used with a different message.
422 invalid Stops the run. code and the error message say which field.
429 throttled catchPostShibaError schedules the retry for the start of the next UTC hour. See What happens at your cap.

Every other status is on Errors.

Next steps