npm install @convex-dev/resend
This component is the official way to integrate the Resend email service with your Convex project.
Features:
See example for a demo of how to incorporate this hook into your application.
npm install @convex-dev/resend
Create a Resend account and grab an API key. Set it to
RESEND_API_KEY
in your deployment environment.
Next, add the component to your Convex app via convex/convex.config.ts
:
import { defineApp } from "convex/server";
import resend from "@convex-dev/resend/convex.config";
const app = defineApp();
app.use(resend);
export default app;
Then you can use it, as we see in convex/sendEmails.ts
:
import { components } from "./_generated/api";
import { Resend } from "@convex-dev/resend";
import { internalMutation } from "./_generated/server";
export const resend: Resend = new Resend(components.resend, {});
export const sendTestEmail = internalMutation({
handler: async (ctx) => {
await resend.sendEmail(
ctx,
"Me <test@mydomain.com>",
"Resend <delivered@resend.dev>",
"Hi there",
"This is a test email"
);
},
});
Then, calling sendTestEmail
from anywhere in your app will send this test email.
If you want to send emails to real addresses, you need to disable testMode
.
You can do this in ResendOptions
, as detailed below.
While the setup we have so far will reliably send emails, you don't have any feedback on anything delivering, bouncing, or triggering spam complaints. For that, we need to set up a webhook!
On the Convex side, we need to mount an http endpoint to our project to route it to
the Resend component in convex/http.ts
:
import { httpRouter } from "convex/server";
import { httpAction } from "./_generated/server";
import { resend } from "./sendEmails";
const http = httpRouter();
http.route({
path: "/resend-webhook",
method: "POST",
handler: httpAction(async (ctx, req) => {
return await resend.handleResendEventWebhook(ctx, req);
}),
});
export default http;
If our Convex project is happy-leopard-123, we now have a Resend webhook for
our project running at https://happy-leopard-123.convex.site/resend-webhook
.
So navigate to the Resend dashboard and create a new webhook at that URL. Make sure
to enable all the email.*
events; the other event types will be ignored.
Finally, copy the webhook secret out of the Resend dashboard and set it to the
RESEND_WEBHOOK_SECRET
environment variable in your Convex deployment.
You should now be seeing email status updates as Resend makes progress on your batches!
Speaking of...
If you have your webhook established, you can also register an event handler in your apps you get notifications when email statuses change.
Update your sendEmails.ts
to look something like this:
import { components, internal } from "./_generated/api";
import { Resend } from "@convex-dev/resend";
import { internalMutation } from "./_generated/server";
import { vEmailId, vEmailEvent, Resend } from "@convex-dev/resend";
export const resend: Resend = new Resend(components.resend, {
onEmailEvent: internal.example.handleEmailEvent,
});
export const handleEmailEvent = internalMutation({
args: {
id: vEmailId,
event: vEmailEvent,
},
handler: async (ctx, args) => {
console.log("Got called back!", args.id, args.event);
// Probably do something with the event if you care about deliverability!
},
});
/* ... existing email sending code ... */
Check out the example/
project in this repo for a full demo.
There is a ResendOptions
argument to the component constructor to help customize
it's behavior.
Check out the docstrings, but notable options include:
apiKey
: Provide the Resend API key instead of having it read from the environment
variable.webhookSecret
: Same thing, but for the webhook secret.testMode
: Only allow delivery to test addresses. To keep you safe as you develop
your project, testMode
is default true. You need to explicitly set this to
false
for the component to allow you to enqueue emails to artibrary addresses.onEmailEvent
: Your email event callback, as outlined above!
Check out the docstrings for details on the events that
are emitted.In addition to basic from/to/subject and html/plain text bodies, the sendEmail
method
allows you to provide a list of replyTo
addresses, and other email headers.
The sendEmail
method returns a branded type, EmailId
. You can use this
for a few things:
resend.status(ctx, emailId)
.resend.cancelEmail(ctx, emailId)
.If the email has already been sent to the Resend API, it cannot be cancelled. Cancellations do not trigger an email event.
This component retains "finalized" (delivered, cancelled, bounced) emails for seven days so you can check on the status of them. Then, a background job clears out those emails and their bodies to reclaim database space.