Send invoice emails with one function call
Render a PDF and email it as an attachment in a single line. No manual byte handling, no attachment wiring.
Every time I've built an invoicing feature, the code looks the same. Generate the PDF. Convert to a Buffer. Build the email. Wire up the attachment. Set the content type. Handle errors for both the render and the send separately. It's 30 lines of glue code that I've written a dozen times.
So I packaged it.
import { sendPdf } from '@formepdf/resend';
const { data, error } = await sendPdf({
resendApiKey: process.env.RESEND_API_KEY,
from: 'Acme Corp <billing@acme.com>',
to: customer.email,
subject: `Invoice #${invoice.number}`,
template: 'invoice',
data: {
company: 'Acme Corp',
customer: customer.name,
invoiceNumber: invoice.number,
items: invoice.lineItems,
total: invoice.total,
},
});
PDF rendered, email sent, invoice attached. One call. Returns Resend's { data, error } directly.

What's actually happening
The package does three things in sequence:
- Loads the invoice template and calls it with your data to get a React element
- Passes that element to Forme's Rust/WASM engine, which returns PDF bytes
- Sends those bytes to Resend's API as a Buffer attachment
No headless browser. No temp files. No intermediate storage. The PDF exists in memory for a few milliseconds between render and send.
If you don't provide email HTML, it generates a sensible default based on the template type. An invoice gets "Hi {customer}, please find your invoice attached." A receipt gets a payment confirmation. You can override with your own html, text, or even a React Email component for the email body.
Pre-rendered bytes
Most backend code renders the PDF separately - you might store it, log it, or attach it to multiple emails. Pass the bytes directly:
import { renderDocument } from '@formepdf/core';
import { sendPdf } from '@formepdf/resend';
import { InvoiceTemplate } from './templates/invoice';
const pdfBytes = await renderDocument(InvoiceTemplate(invoiceData));
// Email it
const { data, error } = await sendPdf({
resendApiKey: process.env.RESEND_API_KEY,
from: 'billing@acme.com',
to: customer.email,
subject: `Invoice #${invoice.number}`,
pdf: pdfBytes,
filename: `invoice-${invoice.number}.pdf`,
});
// Same bytes go to S3, a webhook, wherever
await s3.putObject({ Body: pdfBytes, ... });
Works in plain .ts files - no JSX needed at the call site.
When you need more control
Sometimes you want to add the PDF to an existing email alongside other attachments. The lower-level renderAndAttach function handles that:
import { renderAndAttach } from '@formepdf/resend';
import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY);
const pdf = await renderAndAttach({
template: 'invoice',
data: invoiceData,
filename: `invoice-${invoice.number}.pdf`,
});
await resend.emails.send({
from: 'billing@acme.com',
to: customer.email,
subject: 'Your documents',
html: myCustomEmailHtml,
attachments: [
pdf,
{ filename: 'terms.pdf', path: 'https://example.com/terms.pdf' },
],
});
renderAndAttach returns { filename, content } which is exactly what Resend's attachment API expects. You drop it into the attachments array and you're done.
Custom templates
The package ships with 5 built-in templates: invoice, receipt, report, letter, and shipping label. But you can render any Forme template:
import { sendPdf } from '@formepdf/resend';
import { ProposalTemplate } from './templates/proposal';
const { data, error } = await sendPdf({
resendApiKey: process.env.RESEND_API_KEY,
from: 'sales@acme.com',
to: prospect.email,
subject: 'Proposal for Project Atlas',
render: () => ProposalTemplate(proposalData),
filename: 'proposal-atlas.pdf',
html: '<p>Hi, please find our proposal attached. Looking forward to discussing.</p>',
});
The render function takes any Forme JSX template. If you can render it with renderDocument(), you can email it with sendPdf().
Why Resend
Resend's attachment API is clean. You pass a Buffer as content and a filename and it works. No base64 encoding, no multipart form nonsense. The developer experience matches what we're going for with Forme: things that should be simple are simple.
Also, nobody on Resend's integrations page does PDF generation. There are notification tools, CMS integrations, workflow platforms. But no PDF engine. We're filling a gap that should have been filled a long time ago.
Install
npm install @formepdf/resend resend @formepdf/react @formepdf/core
The Resend SDK is a peer dependency. You probably already have it if you're sending transactional email.