Emails
Sending Emails
Send transactional emails with the sendEmail API
The sendEmail function provides a type-safe way to send emails through Resend.
Basic Usage
import { sendEmail } from "@repo/emails"
const { data, error } = await sendEmail({
template: "TeamInvite",
from: "MyApp <[email protected]>",
to: "[email protected]",
subject: "You've been invited!",
props: {
email: "[email protected]",
invitedByName: "Alex Johnson",
teamName: "Acme Corp",
inviteLink: "https://myapp.com/invite/abc123"
}
})Parameters
| Parameter | Type | Description |
|---|---|---|
template | string | Template name from the registry |
from | string | Sender address |
to | string | Recipient address |
subject | string | Email subject line |
props | object | Template-specific data (type-safe) |
Response
const { data, error } = await sendEmail({ ... })
if (error) {
console.error("Failed to send:", error.message)
return
}
console.log("Email sent:", data?.id)Development Mode
In development (NODE_ENV=development), emails are not sent to Resend. Instead:
- The email renders to HTML
- Opens automatically in your default browser
- Shows sender, recipient, and subject metadata
This enables rapid iteration without API calls.
Force sending in development
Set this env var to send real emails in development:
RESEND_ENABLED=trueIn API Routes
import { sendEmail } from "@repo/emails"
export async function POST(request: Request) {
const { email } = await request.json()
const code = generateVerificationCode()
const { error } = await sendEmail({
template: "VerifyCode",
from: "MyApp <[email protected]>",
to: email,
subject: "Your verification code",
props: {
email,
otpCode: code,
expiryTime: "10 minutes"
}
})
if (error) {
return Response.json(
{ error: "Failed to send verification email" },
{ status: 500 }
)
}
return Response.json({ success: true })
}In Server Actions
"use server"
import { sendEmail } from "@repo/emails"
export async function inviteToTeam(formData: FormData) {
const email = formData.get("email") as string
const teamName = formData.get("teamName") as string
const inviteToken = generateInviteToken()
const inviteLink = `${process.env.NEXT_PUBLIC_APP_URL}/invite/${inviteToken}`
const { error } = await sendEmail({
template: "TeamInvite",
from: "MyApp <[email protected]>",
to: email,
subject: `Join ${teamName} on MyApp`,
props: {
email,
invitedByName: "Current User",
teamName,
inviteLink
}
})
if (error) {
return { error: "Failed to send invitation" }
}
return { success: true }
}Sender Address
The from address should be from a verified domain in Resend. Format options:
// Just email
from: "[email protected]"
// Name and email
from: "MyApp <[email protected]>"
// Support address
from: "MyApp Support <[email protected]>"Error Handling
Common errors and handling:
const { error } = await sendEmail({ ... })
if (error) {
// Log for debugging
console.error("Email error:", error)
// Handle specific cases
if (error.message.includes("rate limit")) {
// Retry later
}
if (error.message.includes("invalid")) {
// Bad email address
}
// Return user-friendly message
return { error: "Unable to send email. Please try again." }
}Batch Sending
For multiple recipients, loop and send individually:
const recipients = ["[email protected]", "[email protected]"]
const results = await Promise.all(
recipients.map(email =>
sendEmail({
template: "Newsletter",
from: "MyApp <[email protected]>",
to: email,
subject: "Weekly Update",
props: { email }
})
)
)
const failed = results.filter(r => r.error)
if (failed.length > 0) {
console.error(`${failed.length} emails failed to send`)
}For large batch sends (100+ emails), use Resend's batch API directly or a job queue to avoid rate limits.
Queue Integration
For production apps, consider using a job queue:
import { sendEmail } from "@repo/emails"
export async function sendEmailJob(payload: {
template: string
to: string
subject: string
props: Record<string, unknown>
}) {
const { error } = await sendEmail({
template: payload.template as "TeamInvite" | "VerifyCode",
from: "MyApp <[email protected]>",
to: payload.to,
subject: payload.subject,
props: payload.props
})
if (error) {
throw new Error(`Failed to send email: ${error.message}`)
}
}This allows retries, rate limiting, and async processing.