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

Send emails with Convex

Install the Convex component and call sendEmail.

What you'll build

A Convex mutation that enqueues a welcome email. The PostShiba component commits the email with your transaction, then delivers it from a background action with retries.

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.

Install

install.sh
1npm install @postshiba/convex

Send your first email

  1. Register the component in convex/convex.config.ts.
convex.config.ts
1import { defineApp } from "convex/server" 2import postshiba from "@postshiba/convex/convex.config" 3 4const app = defineApp() 5app.use(postshiba) 6 7export default app
  1. Create the client and call sendEmail from a mutation in convex/email.ts.
email.ts
1import { mutation } from "./_generated/server" 2import { v } from "convex/values" 3import { PostShiba } from "@postshiba/convex" 4import { components } from "./_generated/api" 5 6export const postshiba = new PostShiba(components.postshiba, { 7 apiKey: "YOUR_API_KEY", 8 teamId: "KjkAJW", 9 clusterId: "NmQpXr", 10 testMode: false, 11}) 12 13export const sendWelcome = mutation({ 14 args: { to: v.string() }, 15 returns: v.string(), 16 handler: async (ctx, args) => { 17 return await postshiba.sendEmail(ctx, { 18 from: "hello@mail.example.com", 19 to: args.to, 20 subject: "Welcome", 21 text: "Glad you are here.", 22 html: "<p>Glad you are here.</p>", 23 }) 24 }, 25})

Run it with npx convex run email:sendWelcome '{"to": "you@example.com"}'. sendEmail returns an EmailId once the email is stored as queued. Delivery happens after the mutation commits, and the message then shows under Events in the dashboard.

You can leave out apiKey, teamId, and clusterId and set POSTSHIBA_API_KEY, POSTSHIBA_TEAM_ID, and POSTSHIBA_CLUSTER_ID with npx convex env set instead.

Two options matter before you send for real:

Option Default Why it matters
testMode true While on, every recipient must be on inbound.postshiba.com. Set it to false to send to real addresses.
hourlyLimit 25 The component sends at most this many emails an hour. Raise it to just under your cluster's hourly capacity.

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 postshiba.sendEmail(ctx, { 2 from: "hello@mail.example.com", 3 to: "you@example.com", 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})

Send a published template

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

template.ts
1await postshiba.sendEmail(ctx, { 2 from: "hello@mail.example.com", 3 to: "you@example.com", 4 template: { id: "welcome", variables: { name: "Ada" } }, 5})

Keep the key safe

Use a Send only key in your deployment. 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 set one on a Convex deployment.
  • Set the key with npx convex env set POSTSHIBA_API_KEY YOUR_API_KEY and leave apiKey out of new PostShiba. Don't commit it, log it, or put it in a URL.
  • The component sends from a background action, so the key stays in your deployment. Never pass a key, even a Send only key, from your React or mobile client.
  • Create one key per deployment, so dev and prod never share one. If a key leaks, delete it on API keys and create a new one. Deleting stops it straight away.

Troubleshooting

Errors do not reach your mutation, because delivery runs later. Read the outcome with postshiba.status(ctx, emailId), or subscribe to webhooks with handleEventWebhook.

Status error What the component does
401 Invalid token or no user signed in Marks the email failed without retrying. Check apiKey or POSTSHIBA_API_KEY.
403 forbidden, domain_unverified, or kyc_required Marks the email failed. 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.
422 invalid Marks the email failed. errorCode and errorMessage on the email say why.
429 throttled Retries with backoff, because the component owns the queue. Keep hourlyLimit under your cluster's capacity so this stays rare. See What happens at your cap.

Every other status is on Errors.

Next steps