← Tools

Convex Autosend

Tags

ConvexEmailTypeScript

Convex component for transactional email delivery on AutoSend—queueing, retries, idempotency, webhook verification, and delivery lifecycle tracking.

A Convex component for transactional email on top of AutoSend. Queue-first sending, deterministic idempotency, retry handling, and full delivery lifecycle.

#Features

  • Queue-first sendingsendEmail 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 lifecyclequeuedsendingsent / 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 & eventsstatusBatch, listEvents, maintenance cleanup (dry-run supported)

#Demo

convex-autosend.vercel.app — live demo of the component.

#Installation

npm install @mzedstudio/autosend convex

#Setup

#1. Register the component

ts
// convex/convex.config.ts
import { defineApp } from "convex/server";
import autosend from "@mzedstudio/autosend/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 "@mzedstudio/autosend";
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 "@mzedstudio/autosend";
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

MethodContextNotes
sendEmailmutationEnqueues one email
sendBulkmutationEnqueues up to 100 recipients
status / statusBatchqueryRead email state
listEventsqueryWebhook events per email
cancelEmailmutationOnly from queued or retrying
processQueueactionSends due queued/retrying emails
cleanupOldEmailsactionRemoves old terminal emails
cleanupAbandonedEmailsactionRecovers stale sending jobs

#Config highlights

FieldDefaultDescription
rateLimitRps2Max sends per queue run
retryDelaysMs[5000, 10000, 20000]Retry schedule
maxAttempts4Total attempts including first
testModetrueRewrites recipients to sandboxTo
View on GitHub·raymond-UI/convex-autosend
GitHub