name: email-notifications
description: Expert guide for sending transactional emails, creating templates, scheduling notifications, and email best practices. Use when implementing email functionality, notifications, or campaigns.
slug: email-notifications
category: operations
complexity: complex
version: "1.0.0"
author: "id8Labs"
triggers:
- "email-notifications"
- "email notifications"
tags:
- development
- tool-factory-retrofitted---
Email Notifications Skill
Core Workflows
Workflow 1: Primary Action
- Analyze the input and context
- Validate prerequisites are met
- Execute the core operation
- Verify the output meets expectations
- Report results
Overview
This skill helps you implement email notifications in your Next.js application. From transactional emails to scheduled campaigns, this covers everything you need for reliable email delivery.
Email Providers
Resend (Recommended for Next.js)
Install:
npm install resend
Setup:
import { Resend } from 'resend'
export const resend = new Resend(process.env.RESEND_API_KEY)
Send Email:
import { resend } from '@/lib/email'
export async function POST(request: Request) {
const { to, subject, html } = await request.json()
try {
const { data, error } = await resend.emails.send({
from: 'noreply@yourdomain.com',
to,
subject,
html,
})
if (error) {
return Response.json({ error }, { status: 400 })
}
return Response.json({ data })
} catch (error) {
return Response.json({ error }, { status: 500 })
}
}
SendGrid
Install:
npm install @sendgrid/mail
Setup:
import sgMail from '@sendgrid/mail'
sgMail.setApiKey(process.env.SENDGRID_API_KEY!)
export async function sendEmail({
to,
subject,
html,
}: {
to: string
subject: string
html: string
}) {
await sgMail.send({
from: 'noreply@yourdomain.com',
to,
subject,
html,
})
}
Nodemailer (SMTP)
Install:
npm install nodemailer
Setup:
import nodemailer from 'nodemailer'
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: parseInt(process.env.SMTP_PORT!),
secure: true,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASSWORD,
},
})
export async function sendEmail({
to,
subject,
html,
}: {
to: string
subject: string
html: string
}) {
await transporter.sendMail({
from: process.env.EMAIL_FROM,
to,
subject,
html,
})
}
React Email (Email Templates)
Install
npm install react-email @react-email/components
npm install -D @react-email/tailwind
Create Email Template
import {
Body,
Button,
Container,
Head,
Heading,
Html,
Link,
Preview,
Section,
Text,
Tailwind,
} from '@react-email/components'
interface WelcomeEmailProps {
name: string
loginUrl: string
}
export function WelcomeEmail({ name, loginUrl }: WelcomeEmailProps) {
return (
<Html>
<Head />
<Preview>Welcome to our platform, {name}!</Preview>
<Tailwind>
<Body className="bg-gray-100 font-sans">
<Container className="bg-white mx-auto my-8 p-8 rounded-lg shadow-lg max-w-2xl">
<Heading className="text-2xl font-bold text-gray-900 mb-4">
Welcome, {name}!
</Heading>
<Text className="text-gray-700 mb-4">
We're excited to have you on board. Get started by logging in to your account.
</Text>
<Section className="text-center my-8">
<Button
href={loginUrl}
className="bg-blue-600 text-white px-6 py-3 rounded-lg font-semibold"
>
Log In to Your Account
</Button>
</Section>
<Text className="text-gray-600 text-sm">
If you didn't create this account, you can safely ignore this email.
</Text>
<Text className="text-gray-500 text-xs mt-8">
© 2024 Your Company. All rights reserved.
<br />
<Link href="https://yourcompany.com/unsubscribe">Unsubscribe</Link>
</Text>
</Container>
</Body>
</Tailwind>
</Html>
)
}
export default WelcomeEmail
Render Email Template
import { render } from '@react-email/components'
import { WelcomeEmail } from '@/emails/welcome'
import { resend } from '@/lib/email'
export async function sendWelcomeEmail(name: string, email: string) {
const html = render(
<WelcomeEmail
name={name}
loginUrl={`${process.env.NEXT_PUBLIC_APP_URL}/login`}
/>
)
await resend.emails.send({
from: 'welcome@yourdomain.com',
to: email,
subject: `Welcome, ${name}!`,
html,
})
}
Preview Emails (Development)
{
"scripts": {
"email:dev": "email dev -p 3001"
}
}
npm run email:dev
Common Email Templates
Password Reset
import {
Body,
Button,
Container,
Head,
Heading,
Html,
Preview,
Section,
Text,
Tailwind,
} from '@react-email/components'
interface PasswordResetEmailProps {
name: string
resetUrl: string
}
export function PasswordResetEmail({ name, resetUrl }: PasswordResetEmailProps) {
return (
<Html>
<Head />
<Preview>Reset your password</Preview>
<Tailwind>
<Body className="bg-gray-100 font-sans">
<Container className="bg-white mx-auto my-8 p-8 rounded-lg max-w-2xl">
<Heading className="text-2xl font-bold mb-4">
Reset Your Password
</Heading>
<Text className="text-gray-700 mb-4">
Hi {name}, we received a request to reset your password.
</Text>
<Section className="text-center my-8">
<Button
href={resetUrl}
className="bg-blue-600 text-white px-6 py-3 rounded-lg"
>
Reset Password
</Button>
</Section>
<Text className="text-gray-600 text-sm">
This link will expire in 1 hour. If you didn't request this, you can safely ignore this email.
</Text>
</Container>
</Body>
</Tailwind>
</Html>
)
}
Order Confirmation
interface OrderConfirmationEmailProps {
name: string
orderId: string
items: Array<{ name: string; price: number; quantity: number }>
total: number
}
export function OrderConfirmationEmail({
name,
orderId,
items,
total,
}: OrderConfirmationEmailProps) {
return (
<Html>
<Head />
<Preview>Order confirmation #{orderId}</Preview>
<Tailwind>
<Body className="bg-gray-100 font-sans">
<Container className="bg-white mx-auto my-8 p-8 rounded-lg max-w-2xl">
<Heading className="text-2xl font-bold mb-4">
Order Confirmed!
</Heading>
<Text>Hi {name}, thanks for your order!</Text>
<Text className="text-gray-600">Order #{orderId}</Text>
<Section className="my-8">
{items.map((item, index) => (
<div key={index} className="flex justify-between py-2 border-b">
<Text>
{item.quantity}x {item.name}
</Text>
<Text>${item.price.toFixed(2)}</Text>
</div>
))}
<div className="flex justify-between py-4 font-bold">
<Text>Total</Text>
<Text>${total.toFixed(2)}</Text>
</div>
</Section>
<Button href={`${process.env.NEXT_PUBLIC_APP_URL}/orders/${orderId}`}>
View Order Details
</Button>
</Container>
</Body>
</Tailwind>
</Html>
)
}
Email Triggers
Welcome Email on Signup
import { sendWelcomeEmail } from '@/lib/emails/send'
export async function POST(request: Request) {
const { email, name } = await request.json()
const user = await createUser({ email, name })
sendWelcomeEmail(name, email).catch((error) => {
console.error('Failed to send welcome email:', error)
})
return Response.json({ user })
}
Order Confirmation
import { render } from '@react-email/components'
import { OrderConfirmationEmail } from '@/emails/order-confirmation'
import { resend } from '@/lib/email'
export async function sendOrderConfirmation(order: Order) {
const html = render(
<OrderConfirmationEmail
name={order.customerName}
orderId={order.id}
items={order.items}
total={order.total}
/>
)
await resend.emails.send({
from: 'orders@yourdomain.com',
to: order.customerEmail,
subject: `Order Confirmation #${order.id}`,
html,
})
}
Scheduled Emails
Daily Digest
import { sendDailyDigest } from '@/lib/emails/send'
export async function GET(request: Request) {
const authHeader = request.headers.get('authorization')
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
return new Response('Unauthorized', { status: 401 })
}
const users = await db.users.findMany({
where: { emailPreferences: { dailyDigest: true } },
})
for (const user of users) {
await sendDailyDigest(user)
}
return Response.json({ sent: users.length })
}
Vercel Cron Configuration
{
"crons": [
{
"path": "/api/cron/daily-digest",
"schedule": "0 9 * * *"
},
{
"path": "/api/cron/weekly-summary",
"schedule": "0 9 * * 1"
}
]
}
Email Queue (Background Jobs)
Using Supabase Edge Functions
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
import { Resend } from 'npm:resend'
const resend = new Resend(Deno.env.get('RESEND_API_KEY'))
serve(async (req) => {
const { to, subject, html } = await req.json()
const { data, error } = await resend.emails.send({
from: 'noreply@yourdomain.com',
to,
subject,
html,
})
return new Response(JSON.stringify({ data, error }), {
headers: { 'Content-Type': 'application/json' },
})
})
Queue Email for Sending
import { createClient } from '@/lib/supabase/server'
export async function queueEmail({
to,
subject,
html,
}: {
to: string
subject: string
html: string
}) {
const supabase = createClient()
await supabase.from('email_queue').insert({
to,
subject,
html,
status: 'pending',
created_at: new Date().toISOString(),
})
}
export async function processEmailQueue() {
const supabase = createClient()
const { data: emails } = await supabase
.from('email_queue')
.select('*')
.eq('status', 'pending')
.limit(10)
for (const email of emails || []) {
try {
await resend.emails.send({
from: 'noreply@yourdomain.com',
to: email.to,
subject: email.subject,
html: email.html,
})
await supabase
.from('email_queue')
.update({ status: 'sent', sent_at: new Date().toISOString() })
.eq('id', email.id)
} catch (error) {
await supabase
.from('email_queue')
.update({
status: 'failed',
error: error.message,
attempts: email.attempts + 1,
})
.eq('id', email.id)
}
}
}
Email Preferences
User Email Settings
type EmailPreferences = {
marketing: boolean
productUpdates: boolean
weeklyDigest: boolean
orderUpdates: boolean
}
export async function updateEmailPreferences(
userId: string,
preferences: Partial<EmailPreferences>
) {
await db.users.update({
where: { id: userId },
data: { emailPreferences: preferences },
})
}
export async function canSendEmail(
userId: string,
emailType: keyof EmailPreferences
): Promise<boolean> {
const user = await db.users.findUnique({
where: { id: userId },
select: { emailPreferences: true },
})
return user?.emailPreferences[emailType] ?? false
}
Unsubscribe Link
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const token = searchParams.get('token')
const userId = await verifyUnsubscribeToken(token)
await db.users.update({
where: { id: userId },
data: {
emailPreferences: {
marketing: false,
productUpdates: false,
},
},
})
return new Response('You have been unsubscribed', { status: 200 })
}
function getUnsubscribeUrl(userId: string): string {
const token = generateUnsubscribeToken(userId)
return `${process.env.NEXT_PUBLIC_APP_URL}/api/unsubscribe?token=${token}`
}
Email Testing
Test Email Locally
import { sendWelcomeEmail } from '@/lib/emails/send'
async function testEmail() {
await sendWelcomeEmail('Test User', 'test@example.com')
console.log('Test email sent!')
}
testEmail()
Email Preview in Browser
import { WelcomeEmail } from '@/emails/welcome'
export default function EmailPreview({
params,
}: {
params: { template: string }
}) {
const templates = {
welcome: <WelcomeEmail name="John Doe" loginUrl="/login" />,
}
return (
<div className="p-8">
{templates[params.template]}
</div>
)
}
Email Analytics
Track Email Opens
export function WelcomeEmail({ name, userId }: { name: string; userId: string }) {
const trackingPixel = `${process.env.NEXT_PUBLIC_APP_URL}/api/email/track/open?userId=${userId}&email=welcome`
return (
<Html>
{/* Email content */}
<img src={trackingPixel} width="1" height="1" alt="" />
</Html>
)
}
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const userId = searchParams.get('userId')
const emailType = searchParams.get('email')
await db.emailEvents.create({
data: {
userId,
emailType,
event: 'open',
timestamp: new Date(),
},
})
const pixel = Buffer.from(
'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7',
'base64'
)
return new Response(pixel, {
headers: {
'Content-Type': 'image/gif',
'Cache-Control': 'no-store',
},
})
}
Track Link Clicks
function createTrackedLink(url: string, userId: string, emailType: string): string {
return `${process.env.NEXT_PUBLIC_APP_URL}/api/email/track/click?url=${encodeURIComponent(url)}&userId=${userId}&email=${emailType}`
}
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const url = searchParams.get('url')
const userId = searchParams.get('userId')
const emailType = searchParams.get('email')
await db.emailEvents.create({
data: {
userId,
emailType,
event: 'click',
metadata: { url },
timestamp: new Date(),
},
})
return Response.redirect(url!, 302)
}
Best Practices Checklist
When to Use This Skill
Invoke this skill when:
- Setting up email notifications
- Creating email templates
- Implementing transactional emails
- Building email campaigns
- Setting up scheduled emails
- Implementing email preferences
- Debugging email delivery
- Adding email tracking
- Creating unsubscribe flows
- Testing email templates