Test and recover
Redirect recipients, use in-memory delivery, inspect failure history, cancel queued work, and retry terminal failures.
Redirect every recipient
Test mode changes the stored message before it is queued. It replaces to, removes cc and bcc, and adds metadata.convexEmailTestMode: true.
await email.setConfig(ctx, {
defaultFrom: "Acme <hello@acme.com>",
testMode: true,
sandboxTo: ["email-preview@acme.com"],
maxAttempts: 2,
retryBaseMs: 1_000,
cleanupAfterDays: 7,
});setConfig replaces the whole document, so include every value you want to keep. Test mode without sandboxTo fails before enqueueing and cannot contact the original recipients.
Use in-memory delivery
export const email = new ConvexEmail(components.convexEmail, {
adapters: [{ kind: "memory" }],
defaultAdapter: "memory",
});The memory adapter exercises the real queue, scheduler, status, event, idempotency, cancellation, and cleanup behavior without a network call.
For convex-test, register the packaged component helper:
import convexEmailTest from "@opencoredev/convex-email/test";
import { convexTest } from "convex-test";
export function createTest() {
const t = convexTest();
convexEmailTest.registerConvexEmail(t);
return t;
}Read the failure before retrying
const status = await email.status(ctx, { emailId });
const events = await email.listEvents(ctx, { emailId });Check lastError, attemptCount, attemptedAdapters, and the ordered events. Fix missing credentials, invalid sender configuration, unsupported message fields, or provider policy errors before starting another cycle.
Retry a terminal failure
import { v } from "convex/values";
import { internalMutation } from "./_generated/server";
import { email } from "./email";
export const retry = internalMutation({
args: { emailId: v.string() },
returns: v.boolean(),
handler: async (ctx, args) => email.retry(ctx, args),
});retry returns true only for a terminal failed record. It resets the attempt counter, clears the terminal error, appends a manual retry_scheduled event, and queues processing immediately. Sent and canceled messages are not retryable.
Use an idempotency key on the original message. Automatic recovery refuses to retry stale in-flight sends without one because the provider may already have accepted the message before the worker lost its result.
Expose only authorized operations
exposeApi() can generate app function definitions for send, batch, status, events, cancellation, and retry. Configuration functions stay excluded unless includeConfigApi: true.
Most applications should write explicit wrappers instead. That keeps authentication, tenant checks, recipient policy, and administrator-only recovery visible in application code.
Send and track email
Understand queue state and event history.
Test core Email SDK
Use memory and failing adapters outside Convex.
