Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.autosend.com/llms.txt

Use this file to discover all available pages before exploring further.

Official Convex component

Overview

The official AutoSend Convex component is a drop-in integration for sending transactional email from your Convex backend. It wraps the AutoSend API with queueing, deterministic idempotency, automatic retries, webhook verification, and full delivery lifecycle tracking, so you only need to call sendEmail from a mutation and the component handles the rest. The package is open source on GitHub and published on npm as @autosend/convex.
The component runs entirely inside your Convex deployment. Email jobs, retry state, and webhook events are stored in Convex tables, so you do not need any extra infrastructure to track delivery.

Install with your AI assistant

If you use an AI coding assistant (Cursor, Claude Code, Copilot, Codex, etc.), paste the prompt below into your editor. It points the assistant at the official Convex component docs and llms.txt, then asks it to produce a setup checklist tailored to your project.
Install prompt
Help me install the AutoSend component.

Package: @autosend/convex
Install: npm install @autosend/convex

Documentation:
- https://www.convex.dev/components/autosend/convex/convex.md
- https://www.convex.dev/components/autosend/convex/llms.txt

Please:
1. Retrieve the install command and documentation
2. Generate an exact setup checklist for this component
3. List any required environment variables
4. Provide verification steps
Prefer to wire it up yourself? Skip ahead to Install and follow the manual steps.

What you get

  • Queue-first sending. sendEmail and sendBulk enqueue jobs and automatically trigger queue processing.
  • Idempotency. Duplicate requests resolve to the same emailId, so retried mutations never double-send.
  • Retries with backoff. Network errors, 429, and 5xx responses are retried on a configurable schedule.
  • Lifecycle tracking. Every email moves through queued, sending, retrying, sent, failed, or canceled.
  • Webhook ingestion. HMAC SHA-256 signature verification, timestamp skew protection, and per-delivery dedupe out of the box.
  • Templates, CC/BCC, attachments, and unsubscribe groups are all supported.
  • Test sandbox. Optional sandboxTo rewrites recipients while you are still wiring things up.

Prerequisites

Before you begin, make sure you have:

Install

Add the component and the Convex SDK to your project:
npm install @autosend/convex convex

Setup

1

Register the component

Add the component to your Convex app definition.
convex/convex.config.ts
import { defineApp } from "convex/server";
import autosend from "@autosend/convex/convex.config.js";

const app = defineApp();
app.use(autosend, { name: "autosend" });
export default app;
2

Create a client wrapper

Wrap the generated component reference in an AutoSend instance you can reuse from any Convex function.
convex/email.ts
import { AutoSend } from "@autosend/convex";
import { components } from "./_generated/api";

export const autosend = new AutoSend(components.autosend);
3

Set your secrets

Push your AutoSend API key and webhook signing secret to the Convex deployment environment. Both are read by the component at runtime.
npx convex env set AUTOSEND_API_KEY <your-api-key>
npx convex env set AUTOSEND_WEBHOOK_SECRET <your-webhook-secret>
The webhook secret is shown when you create a webhook endpoint in the AutoSend dashboard. See Verifying webhook requests for details.
4

Persist component config

The component keeps its own config in a Convex table so it can read settings from queries, mutations, and actions. Call setConfig once from a mutation to write it.
convex/admin.ts
import { mutation } from "./_generated/server";
import { autosend } from "./email";

export const configureAutosend = mutation({
  args: {},
  handler: async (ctx) => {
    await autosend.setConfig(ctx, {
      config: {
        autosendApiKey: process.env.AUTOSEND_API_KEY!,
        webhookSecret: process.env.AUTOSEND_WEBHOOK_SECRET!,
        defaultFrom: "[email protected]",
        testMode: true,
        sandboxTo: ["[email protected]"],
      },
    });
  },
});
Run the mutation once from the Convex dashboard, or call it from a setup script. Subsequent calls merge new values into the existing config unless you pass replace: true.
Leave testMode: true while you are wiring things up. Every send will be rewritten to the addresses in sandboxTo, so you cannot accidentally email real users. Flip it to false when you are ready to go live.
5

Mount the webhook route

The component ships an HTTP handler that verifies AutoSend webhook signatures and updates the email status table. Register it on your Convex HTTP router.
convex/http.ts
import { httpRouter } from "convex/server";
import { registerRoutes } from "@autosend/convex";
import { components } from "./_generated/api";

const http = httpRouter();
registerRoutes(http, components.autosend);
export default http;
The route is mounted at /webhooks/autosend by default. After your next npx convex deploy, point your AutoSend webhook endpoint at:
https://YOUR-DEPLOYMENT.convex.site/webhooks/autosend
Pass { path: "/custom/path" } as a third argument to registerRoutes if you need a different URL.

Send your first email

Call sendEmail from any Convex mutation. The component enqueues the job and triggers the processor automatically, so you do not need to schedule anything yourself.
convex/sendWelcome.ts
import { mutation } from './_generated/server';
import { v } from 'convex/values';
import { autosend } from './email';

export const sendWelcome = mutation({
	args: { email: v.string(), name: v.string() },
	handler: async (ctx, { email, name }) => {
		const { emailId } = await autosend.sendEmail(ctx, {
			to: [email],
			toName: name,
			subject: 'Welcome to Acme',
			html: `<h1>Hi ${name}</h1><p>Thanks for signing up.</p>`,
		});

		return emailId;
	},
});
If you omit from, the component uses the defaultFrom you set in config. The returned emailId is the handle you use to query status and webhook events later.

Sending variants

Reference an AutoSend email template by ID and pass merge fields via dynamicData.
await autosend.sendEmail(ctx, {
  to: ["[email protected]"],
  templateId: "tmpl_welcome",
  dynamicData: {
    firstName: "Jane",
    loginUrl: "https://app.example.com/login",
  },
});
When you use templateId, subject and html are optional. See template variables for the syntax supported in your template.
await autosend.sendEmail(ctx, {
  to: ["[email protected]"],
  cc: [{ email: "[email protected]", name: "Support Team" }],
  bcc: [{ email: "[email protected]" }],
  replyTo: "[email protected]",
  replyToName: "Acme Support",
  subject: "Your order",
  html: "<p>Thanks for your purchase.</p>",
});
Attach files inline as base64 or by URL.
await autosend.sendEmail(ctx, {
  to: ["[email protected]"],
  subject: "Your invoice",
  html: "<p>Invoice attached.</p>",
  attachments: [
    {
      filename: "invoice.pdf",
      fileUrl: "https://files.example.com/invoice.pdf",
      contentType: "application/pdf",
    },
    {
      filename: "summary.csv",
      content: "base64-encoded-content",
      contentType: "text/csv",
    },
  ],
});
Provide either fileUrl or content, never both.
Send the same payload to up to 100 recipients in one call.
await autosend.sendBulk(ctx, {
  recipients: ["[email protected]", "[email protected]"],
  subject: "Product update",
  html: "<p>We shipped new features this week.</p>",
});
Each recipient is queued as a separate email, so retries and webhook events are tracked per recipient.
Pass recipientData keyed by email to interpolate {{placeholders}} into subject, html, and text per recipient.
await autosend.sendBulk(ctx, {
  recipients: ["[email protected]", "[email protected]"],
  recipientData: {
    "[email protected]": { name: "Alice", role: "admin" },
    "[email protected]": { name: "Bob", role: "member" },
  },
  subject: "Welcome, {{name}}",
  html: "<p>Hi {{name}}, you are now a {{role}}.</p>",
});
Per-recipient values are also forwarded as dynamicData for that recipient, overriding any shared dynamicData you pass.
Pass idempotencyKey to guarantee a single send for a given logical operation. If your mutation runs twice (Convex retries on conflict), the second call returns the same emailId with deduped: true.
await autosend.sendEmail(ctx, {
  to: ["[email protected]"],
  subject: "Order #1234 confirmation",
  html: "<p>Thanks for your order.</p>",
  idempotencyKey: `order:1234:confirmation`,
});
Without an explicit key, the component derives one from the payload, so identical payloads also deduplicate.

Track status and events

The component stores every email and its webhook events in Convex, so you can query them from any Convex query.
convex/emailStatus.ts
import { query } from './_generated/server';
import { v } from 'convex/values';
import { autosend } from './email';

export const getEmailStatus = query({
	args: { emailId: v.string() },
	handler: async (ctx, { emailId }) => {
		const email = await autosend.status(ctx, { emailId });
		const events = await autosend.listEvents(ctx, { emailId, limit: 20 });
		return { email, events };
	},
});
Other status helpers:
// Batch status for multiple emails in one call
const statuses = await autosend.statusBatch(ctx, {
	emailIds: [id1, id2, id3],
});

// Cancel an email that has not been sent yet
const { canceled } = await autosend.cancelEmail(ctx, { emailId });
cancelEmail only works while the email is in queued or retrying. Once it reaches sending, sent, or failed, it is locked in.

Email lifecycle

StatusMeaning
queuedAccepted and waiting to be claimed by the processor
sendingCurrently being sent by the queue processor
retryingPrevious attempt failed and a retry is scheduled
sentAccepted by AutoSend
failedRetries exhausted or non-retryable error
canceledCanceled via cancelEmail before send
Default retry policy is 4 attempts total with delays of 5s, 10s, and 20s. Both are tunable via maxAttempts and retryDelaysMs in config.

Configuration

setConfig accepts these fields. Anything you omit keeps its existing value (unless you pass replace: true).
FieldTypeDefaultDescription
autosendApiKeystringunsetBearer token for the AutoSend API
webhookSecretstringunsetHMAC secret used to verify incoming webhooks
defaultFromstringunsetFallback sender address when from is omitted
defaultReplyTostringunsetFallback reply-to address
testModebooleantrueRewrites every recipient to sandboxTo
sandboxTostring[][]Recipients used when testMode is enabled
rateLimitRpsnumber2Max sends per queue run
retryDelaysMsnumber[][5000, 10000, 20000]Delay schedule between retries
maxAttemptsnumber4Total attempts including the first try
sendBatchSizenumber25Max queue items processed per run
autosendBaseUrlstringhttps://api.autosend.comAPI base URL (override for staging)
projectIdstringunsetRequired when using an Account API Key (ASA_ prefix)
To read the current config (without leaking secrets), use getConfig. It returns every value above except autosendApiKey and webhookSecret, plus hasApiKey and hasWebhookSecret booleans you can use in admin UIs.
const safeConfig = await autosend.getConfig(ctx);
AutoSend supports both Project API Keys (AS_ prefix, scoped to one project) and Account API Keys (ASA_ prefix, cross-project). If you use an Account API Key, set projectId in config so every request includes the matching project header.

Go live checklist

Before flipping testMode to false:
  1. Verify your sending domain in the AutoSend dashboard (domain setup).
  2. Deploy your Convex backend so the webhook route is reachable.
  3. Add the webhook URL https://YOUR-DEPLOYMENT.convex.site/webhooks/autosend to your AutoSend project and copy the signing secret into AUTOSEND_WEBHOOK_SECRET.
  4. Send a test email with testMode: true and confirm the status moves to sent and you see email.delivered in listEvents.
  5. Update config with testMode: false and a real defaultFrom.

Troubleshooting

sendEmail triggers the processor automatically, but if a deploy was interrupted you can sweep the queue manually:
await autosend.processQueue(ctx, { batchSize: 25 });
You can also schedule it as a Convex cron job to act as a safety net.
Make sure the webhookSecret in your component config matches the secret shown in the AutoSend dashboard for that exact endpoint. The component validates HMAC SHA-256 over the raw body and rejects timestamps older than 2 minutes. See verifying webhook requests for the full spec.
You are using an Account API Key (ASA_ prefix) without setting projectId. Either switch to a Project API Key (AS_ prefix), or add projectId to your config.
testMode is still true. Every send is being rewritten to sandboxTo. Update your config with testMode: false to send to real recipients.

Resources