---
title: "Convex Autosend"
description: "Convex component for transactional email on AutoSend: queueing, retries, and webhooks."
slug: "convex-autosend"
tags: ["Convex", "Email", "TypeScript"]
url: "https://github.com/autosendhq/autosend-convex"
repo: "autosendhq/autosend-convex"
embedUrl: "https://convex-autosend.vercel.app"
---


A [Convex component](https://docs.convex.dev/components) for transactional email on top of AutoSend. Queue-first sending, deterministic idempotency, retry handling, and full delivery lifecycle.

## Features

- **Queue-first sending** — `sendEmail` and `sendBulk` enqueue jobs; trigger `processQueue` from action/cron to send
- **Deterministic idempotency** — Duplicate requests resolve to the same `emailId`
- **Retry handling** — Network, 429, 5xx retried with configurable backoff (default: 5s, 10s, 20s)
- **Delivery lifecycle** — `queued` → `sending` → `sent` / `failed` / `canceled`
- **CC/BCC, attachments, templates** — Full sender/recipient options, unsubscribe groups
- **Webhook security** — HMAC SHA-256 validation, timestamp skew limit, dedupe by `deliveryId`
- **Batch status & events** — `statusBatch`, `listEvents`, maintenance cleanup (dry-run supported)

## Demo

[convex-autosend.vercel.app](https://convex-autosend.vercel.app) — live demo of the component.

## Installation

<ToolInstall cmd="npm install @autosend/convex convex" />

## Setup

### 1. Register the component

```ts
// 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

```ts
// convex/email.ts
import { AutoSend } from "@autosend/convex";
import { components } from "./_generated/api";

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

### 3. Configure secrets and runtime

```bash
npx convex env set AUTOSEND_API_KEY <api-key>
npx convex env set AUTOSEND_WEBHOOK_SECRET <webhook-secret>
```

Then persist config (e.g. via a one-time mutation):

```ts
await autosend.setConfig(ctx, {
  config: {
    autosendApiKey: "replace-with-your-key",
    webhookSecret: "replace-with-your-webhook-secret",
    defaultFrom: "noreply@example.com",
    testMode: true,
    sandboxTo: ["sandbox@example.com"],
  },
});
```

### 4. Mount webhook route

```ts
// 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;
```

Default path: `/webhooks/autosend`.

## Usage

### Send and process queue

`sendEmail` / `sendBulk` enqueue only. Trigger `processQueue` from an action or cron:

```ts
export const sendWelcome = mutation({
  handler: async (ctx) => {
    return await autosend.sendEmail(ctx, {
      to: ["user@example.com"],
      toName: "Jane Doe",
      subject: "Welcome",
      html: "<p>Hello</p>",
    });
  },
});

export const processEmailQueue = action({
  handler: async (ctx) => {
    return await autosend.processQueue(ctx);
  },
});
```

### Bulk send

```ts
await autosend.sendBulk(ctx, {
  recipients: ["a@example.com", "b@example.com"],
  subject: "Update",
  html: "<p>News</p>",
});
```

### CC, BCC, attachments

```ts
await autosend.sendEmail(ctx, {
  to: ["user@example.com"],
  cc: [{ email: "team@example.com", name: "Team" }],
  subject: "Report",
  html: "<p>See attached.</p>",
  attachments: [
    { filename: "report.pdf", fileUrl: "https://example.com/report.pdf" },
    { filename: "data.csv", content: "base64...", contentType: "text/csv" },
  ],
  unsubscribeGroupId: "marketing",
});
```

### Templates

```ts
await autosend.sendEmail(ctx, {
  to: ["user@example.com"],
  templateId: "welcome-template-id",
  dynamicData: { firstName: "Jane", plan: "Pro" },
});
```

### Status, batch status, events

```ts
const email = await autosend.status(ctx, { emailId });
const statuses = await autosend.statusBatch(ctx, {
  emailIds: [id1, id2, id3],
});
const events = await autosend.listEvents(ctx, { emailId, limit: 20 });
await autosend.cancelEmail(ctx, { emailId });
```

### Cleanup

```ts
const preview = await autosend.cleanupOldEmails(ctx, { dryRun: true });
await autosend.cleanupOldEmails(ctx, { olderThanMs: 7 * 24 * 60 * 60 * 1000 });
await autosend.cleanupAbandonedEmails(ctx, { staleAfterMs: 15 * 60 * 1000 });
await autosend.cleanupOldDeliveries(ctx, { olderThanMs: 7 * 24 * 60 * 60 * 1000 });
```

## API Summary

| Method | Context | Notes |
|--------|---------|-------|
| `sendEmail` | mutation | Enqueues one email |
| `sendBulk` | mutation | Enqueues up to 100 recipients |
| `status` / `statusBatch` | query | Read email state |
| `listEvents` | query | Webhook events per email |
| `cancelEmail` | mutation | Only from `queued` or `retrying` |
| `processQueue` | action | Sends due queued/retrying emails |
| `cleanupOldEmails` | action | Removes old terminal emails |
| `cleanupAbandonedEmails` | action | Recovers stale sending jobs |

## Config highlights

| Field | Default | Description |
|-------|---------|-------------|
| `rateLimitRps` | 2 | Max sends per queue run |
| `retryDelaysMs` | [5000, 10000, 20000] | Retry schedule |
| `maxAttempts` | 4 | Total attempts including first |
| `testMode` | true | Rewrites recipients to `sandboxTo` |

<ToolRepo href="https://github.com/autosendhq/autosend-convex" title="View on GitHub" />
