Next.js SMTP Integration
Using SMTP with Next.js (App Router and Pages Router)
Next.js SMTP Integration
Learn how to integrate SMTP functionality with Next.js using both the App Router and Pages Router approaches.
Installation
npm install nodemailer
npm install -D @types/nodemailer
Configuration
Add SMTP configuration to your environment variables (.env.local):
SMTP_HOST=smtp.shoutbox.net
SMTP_PORT=587
SMTP_USER=shoutbox
SMTP_PASS=your_api_key_here
SMTP Client Setup
// lib/smtp.ts
import nodemailer from "nodemailer";
export const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: parseInt(process.env.SMTP_PORT || "587"),
secure: process.env.SMTP_PORT === "465",
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
});
export interface EmailRequest {
from: string;
to: string;
subject: string;
html: string;
attachments?: Array<{
filename: string;
content: Buffer;
}>;
}
export async function sendEmail(request: EmailRequest) {
return transporter.sendMail(request);
}
App Router Integration
API Route Handler (Route Handlers)
// app/api/send-email/route.ts
import { sendEmail } from "@/lib/smtp";
import { NextResponse } from "next/server";
export async function POST(request: Request) {
try {
const { to, subject, html } = await request.json();
const result = await sendEmail({
from: "your-app@domain.com",
to,
subject,
html,
});
return NextResponse.json({ success: true, messageId: result.messageId });
} catch (error) {
console.error("SMTP Error:", error);
return NextResponse.json(
{ success: false, error: error.message },
{ status: 500 }
);
}
}
Server Component Usage
// app/contact/page.tsx
import { sendEmail } from "@/lib/smtp";
async function ContactPage() {
async function sendContactEmail(formData: FormData) {
"use server";
try {
await sendEmail({
from: "your-app@domain.com",
to: formData.get("email") as string,
subject: "Contact Form Submission",
html: formData.get("message") as string,
});
} catch (error) {
console.error("Failed to send email:", error);
throw new Error("Failed to send email");
}
}
return (
<form action={sendContactEmail}>
<input type="email" name="email" required />
<textarea name="message" required></textarea>
<button type="submit">Send</button>
</form>
);
}
export default ContactPage;
File Attachments in Server Actions
// app/upload/page.tsx
import { sendEmail } from "@/lib/smtp";
async function UploadPage() {
async function sendWithAttachment(formData: FormData) {
"use server";
const file = formData.get("file") as File;
const buffer = Buffer.from(await file.arrayBuffer());
try {
await sendEmail({
from: "your-app@domain.com",
to: formData.get("email") as string,
subject: "File Attachment",
html: "<h1>Your file is attached</h1>",
attachments: [
{
filename: file.name,
content: buffer,
},
],
});
} catch (error) {
console.error("Failed to send email:", error);
throw new Error("Failed to send email");
}
}
return (
<form action={sendWithAttachment}>
<input type="email" name="email" required />
<input type="file" name="file" required />
<button type="submit">Send</button>
</form>
);
}
export default UploadPage;
Pages Router Integration
API Route
// pages/api/send-email.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { sendEmail } from "@/lib/smtp";
export const config = {
api: {
bodyParser: {
sizeLimit: "10mb",
},
},
};
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== "POST") {
return res.status(405).json({ message: "Method not allowed" });
}
try {
const { to, subject, html } = req.body;
const result = await sendEmail({
from: "your-app@domain.com",
to,
subject,
html,
});
res.status(200).json({ success: true, messageId: result.messageId });
} catch (error) {
console.error("SMTP Error:", error);
res.status(500).json({ success: false, error: error.message });
}
}
File Upload API Route
// pages/api/send-with-attachment.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { sendEmail } from "@/lib/smtp";
import formidable from "formidable";
import { createReadStream } from "fs";
export const config = {
api: {
bodyParser: false,
},
};
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== "POST") {
return res.status(405).json({ message: "Method not allowed" });
}
try {
const form = formidable({});
const [fields, files] = await form.parse(req);
const file = files.file?.[0];
if (!file) {
return res.status(400).json({ error: "No file uploaded" });
}
const result = await sendEmail({
from: "your-app@domain.com",
to: fields.email?.[0] || "",
subject: "File Attachment",
html: "<h1>Your file is attached</h1>",
attachments: [
{
filename: file.originalFilename || "attachment",
content: createReadStream(file.filepath),
},
],
});
res.status(200).json({ success: true, messageId: result.messageId });
} catch (error) {
console.error("SMTP Error:", error);
res.status(500).json({ success: false, error: error.message });
}
}
Best Practices
Security
- Always keep SMTP credentials in environment variables
- Validate email inputs on both client and server
- Implement rate limiting for your API routes
- Use appropriate CORS settings
- Validate file uploads and implement size limits
Error Handling
// lib/errors.ts
export class SMTPError extends Error {
constructor(
message: string,
public code?: string,
public responseCode?: number
) {
super(message);
this.name = "SMTPError";
}
}
// Reusable error handler
const handleSMTPError = (error: unknown) => {
console.error("SMTP Error:", error);
if (error instanceof SMTPError) {
switch (error.code) {
case "ECONNECTION":
return "Failed to connect to email server";
case "EAUTH":
return "Authentication failed";
default:
return "Failed to send email";
}
}
return "An unexpected error occurred";
};
Rate Limiting
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { rateLimit } from "./lib/rate-limit";
export async function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith("/api/send-email")) {
const ip = request.ip ?? "127.0.0.1";
const { success } = await rateLimit(ip);
if (!success) {
return NextResponse.json({ error: "Too many requests" }, { status: 429 });
}
}
return NextResponse.next();
}
TypeScript Types
// types/email.ts
export interface EmailAttachment {
filename: string;
content: Buffer | NodeJS.ReadableStream;
contentType?: string;
}
export interface EmailRequest {
from: string;
to: string;
subject: string;
html: string;
attachments?: EmailAttachment[];
}
export interface EmailResponse {
success: boolean;
messageId?: string;
error?: string;
}
Testing
// __tests__/api/send-email.test.ts
import { createMocks } from "node-mocks-http";
import handler from "../../pages/api/send-email";
jest.mock("nodemailer", () => ({
createTransport: jest.fn().mockReturnValue({
sendMail: jest.fn().mockResolvedValue({
messageId: "test-message-id",
}),
}),
}));
describe("/api/send-email", () => {
it("sends email successfully", async () => {
const { req, res } = createMocks({
method: "POST",
body: {
to: "test@example.com",
subject: "Test",
html: "<p>Test content</p>",
},
});
await handler(req, res);
expect(res._getStatusCode()).toBe(200);
expect(JSON.parse(res._getData())).toEqual(
expect.objectContaining({
success: true,
messageId: expect.any(String),
})
);
});
});
Rate Limits
- 60 requests per minute per API key
- Maximum attachment size: 10MB
- Maximum recipients per email: 50
Please contact support if you need higher limits for your use case.
Support
For additional support or questions, please contact our support team.