Schedule emails with Resend
By combining Defer and Resend, send and schedule rich-styled emails in minutes, while keeping full control over their scheduling with cancellations and the Defer Console.
This guide will show you how to implement a monthly usage report email with an attached PDF invoice:

Resend, combined with @react-pdf/renderer
will help in sending rich
emails.
Defer will help in scheduling monthly emails while respecting Resend’s API rate limiting and avoiding duplicate sends .
Implementing the usage email
A rich email with JSX
Resend combined with its
@react-email/components
,
Tailwind and Defer’s support for JSX enables us to
write our monthly usage email with React as follows:
The <MonthlyUsageEmail />
component is then provided to our
sendMonthlyUsage()
function (example in a NextJS project):
import { Resend } from "resend";
import MonthlyUsageEmail from "@/emails/MonthlyUsage";
const resend = new Resend(process.env.RESEND_API_KEY!);
async function sendMonthlyUsage(userId: string) {
const billingInfo = // ... query DB with `userId`
const usageInfo = // ... query DB with `userId`
const title = `You performed ${new Intl.NumberFormat().format(
usageInfo.executions
)} executions in ${billingInfo.period}`;
await resend.emails.send({
from: "usage@defer.run",
to: "charly@defer.run",
subject: title,
html: title,
react: MonthlyUsageEmail({
firstName: billingInfo.user.firstName,
title,
percentPlan: usageInfo.planPercent,
period: billingInfo.period,
}),
});
}
Attach the PDF to the email
Our monthly usage email comes with a PDF file listing all executions performed in the given period.
@react-pdf/renderer
, similarly to
@react-email/components
enable us to create a PDF file using React components,
as follows:
+import React from "react";
import { Resend } from "resend";
+import {
+ Page,
+ Text,
+ View,
+ Document,
+ StyleSheet,
+ renderToBuffer,
+} from "@react-pdf/renderer";
import MonthlyUsageEmail from "@/emails/MonthlyUsage";
+const styles = StyleSheet.create({
+ // ...
+});
+
+// PDF file listing the executions with associated usage
+const ExecutionsDetail = () => (
+ <Document>
+ <Page size="A4" style={styles.page}>
+ // ...
+ </Page>
+ </Document>
+);
+
const resend = new Resend(process.env.RESEND_API_KEY!);
async function sendMonthlyUsage() {
+ const pdf = await renderToBuffer(<ExecutionsDetail />);
+
const title = `You performed ${new Intl.NumberFormat().format(
usageInfo.executions
)} executions in ${billingInfo.period}`;
@@ -19,5 +57,16 @@
percentPlan: usageInfo.planPercent,
period: billingInfo.period,
}),
+ attachments: [
+ {
+ content: pdf,
+ filename: "detail.pdf",
+ },
+ ],
});
}
Resend accepts an attachments
option taking an array of files (Buffer
and
file’s name).
Transform sendMonthlyUsage()
into a Background Function
We simply wrap sendMonthlyUsage()
with
defer()
to transform it into a Defer
Background Function:
import { Resend } from "resend";
+import { defer } from "@defer/client";
import MonthlyUsageEmail from "@/emails/MonthlyUsage";
const resend = new Resend(process.env.RESEND_API_KEY!);
@@ -21,3 +22,8 @@
}),
});
}
+
+export default defer(sendMonthlyUsage, {
+ concurrency: 10,
+ retry: 5,
+});
You’ll notice that we pass to defer()
2 options:
- a
concurrency: 10
to match Resend’s 10 calls/sec policy - a
retry
strategy to recover potential errors
Our sendMonthlyUsage()
is now ready to be called to schedule emails.
Scheduling the monthly usage emails
Each customer has a dedicated sliding usage window (e.g.: From August 15th to September 16th).
Send the usage email on a sliding window schedule
We’ll leverage Defer’s delay()
feature to
match this criteria:
import { delay } from "@defer/client";
import sendMonthlyUsage from "@/defer/sendMonthlyUsage";
export const POST = () => {
const { current_period_end } = // ... create Subscription in Stripe
const delayedsendMonthlyUsage = delay(sendMonthlyUsage, new Date(current_period_end));
await delayedsendMonthlyUsage(userId);
}
Each call to sendMonthlyUsage()
from the NextJS App will trigger a background
execution on Defer.
We need to update sendMonthlyUsage()
to reschedule itself for the next billing
period, again by using delay()
from @defer/client
:
@@ -1,6 +1,6 @@
import React from "react";
import { Resend } from "resend";
-import { defer } from "@defer/client";
+import { defer, delay } from "@defer/client";
import {
Page,
Text,
@@ -41,10 +41,12 @@
async function sendMonthlyUsage(userId: string) {
const pdf = await renderToBuffer(<ExecutionsDetail />);
const billingInfo = // ... query DB with `userId`
const usageInfo = // ... query DB with `userId`
const title = `You performed ${new Intl.NumberFormat().format(
usageInfo.executions
)} executions in ${billingInfo.period}`;
await resend.emails.send({
from: "onboarding@resend.dev",
@@ -52,10 +54,10 @@
subject: title,
html: title,
react: MonthlyUsageEmail({
firstName: billingInfo.user.firstName,
title,
percentPlan: usageInfo.planPercent,
period: billingInfo.period,
}),
attachments: [
{
@@ -64,9 +66,14 @@
},
],
});
+
+ const delayedsendMonthlyUsage = delay(deferSendMonthlyUsage, new Date(billingInfo.current_period_end));
+ await delayedsendMonthlyUsage(userId);
}
-export default defer(sendMonthlyUsage, {
+const deferSendMonthlyUsage = defer(sendMonthlyUsage, {
concurrency: 10,
retry: 5,
});
+
+export default deferSendMonthlyUsage;
What happened here?
The sendMonthlyUsage()
became a recursive
Background Function, scheduling
itself for the next usage period. To achieve this, we needed to move
defer(sendMonthlyUsage)
to the deferSendMonthlyUsage
variable so we could
pass it to delay()
.
Handling unsubscribed customers
Customers do churn and we might want to stop sending them usage emails if they drop in the middle of a billing cycle.
For this, we will leverage Defer’s cancellations mechanism.
First, let’s store the Execution ID of a planned monthly usage email, by
updating sendMonthlyUsage()
as follows:
@@ -68,7 +68,9 @@
});
const delayedsendMonthlyUsage = delay(deferSendMonthlyUsage, new Date(billingInfo.current_period_end));
- await delayedsendMonthlyUsage(userId);
+ const { id: nextEmailIdExecutionId } = await delayedsendMonthlyUsage(userId);
+
+ // save `nextEmailIdExecutionId` to the database
}
const deferSendMonthlyUsage = defer(sendMonthlyUsage, {
Then, let’s update our unsubscribe API Routes to cancel any pending monthly usage email:
import { cancelExecution } from "@defer/client";
import sendMonthlyUsage from "@/defer/sendMonthlyUsage";
export const POST = () => {
// ... cancel Subscription in Stripe
const nextEmailIdExecutionId = // query using `session.userId`
cancelExecution(nextEmailIdExecutionId);
};
Wrapping up
Our application now provides rich and personalized monthly usage emails to our customers.
The Defer Console is your best companion, making it easy to oversee all usage emails:

Or manage specific ones:

Was this page helpful?