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: "[email protected]",
      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: "[email protected]",
        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: "[email protected]",
        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: "[email protected]",
      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: "[email protected]",
      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

  1. Always keep SMTP credentials in environment variables
  2. Validate email inputs on both client and server
  3. Implement rate limiting for your API routes
  4. Use appropriate CORS settings
  5. 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: "[email protected]",
        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

The same rate limits apply to both SMTP and REST API usage:

  • 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.