INTEGRATIONS · Published 2026-10-06 · Updated 2026-10-06 · SecureSMTP
Next.js
Send from a route handler and from a Server Action.
Send from a route handler or a Server Action. Both run on the server, so the API key never reaches the browser. These examples use the App Router (Next.js 14 and 15).
Setup
Add the key to .env.local (and to your host’s environment variables in production). Do not prefix it with NEXT_PUBLIC_ — that would put it in the browser bundle.
SECURESSMTP_API_KEY=qcs_live_...Then add a small send function. Import it only from server code: route handlers, Server Actions and server components.
// lib/securessmtp.ts — import this only from server code
const API_URL = 'https://securessmtp.com/api/v1/mail/send';
export type EmailMessage = {
to: string | string[];
subject: string;
html?: string;
text?: string;
cc?: string | string[];
bcc?: string | string[];
reply_to?: string | string[];
from?: { name: string };
source_plugin?: string;
};
export type SendResult = {
ok: boolean;
sent?: boolean;
held?: boolean;
mode?: 'relay' | 'domain';
message_id?: string;
reason?: string;
error?: string;
};
export class SecureSMTPError extends Error {
readonly reason: string;
readonly status: number;
constructor(reason: string, status: number) {
super(`SecureSMTP: ${reason} (HTTP ${status})`);
this.name = 'SecureSMTPError';
this.reason = reason;
this.status = status;
}
}
export async function sendEmail(message: EmailMessage): Promise<SendResult> {
const apiKey = process.env.SECURESSMTP_API_KEY;
if (!apiKey) throw new Error('SECURESSMTP_API_KEY is not set');
const res = await fetch(API_URL, {
method: 'POST',
headers: {
'x-securessmtp-api-key': apiKey,
'Content-Type': 'application/json',
},
body: JSON.stringify(message),
signal: AbortSignal.timeout(30_000),
});
const result = (await res.json().catch(() => ({ ok: false }))) as SendResult;
if (!result.ok) {
throw new SecureSMTPError(result.reason ?? result.error ?? 'request_failed', res.status);
}
return result;
}Route handler
A contact endpoint that the browser can post JSON to:
// app/api/contact/route.ts
import { NextResponse } from 'next/server';
import { sendEmail } from '@/lib/securessmtp';
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
export async function POST(request: Request) {
const body = await request.json().catch(() => null);
const name = String(body?.name ?? '').trim();
const email = String(body?.email ?? '').trim();
const message = String(body?.message ?? '').trim();
if (!name || name.length > 100 || !EMAIL_RE.test(email) || !message || message.length > 5000) {
return NextResponse.json({ ok: false, error: 'Please fill in every field.' }, { status: 422 });
}
try {
await sendEmail({
to: '[email protected]',
subject: `Contact form: ${name}`,
text: `Name: ${name}\nEmail: ${email}\n\n${message}`,
from: { name: 'Website contact form' },
reply_to: email,
source_plugin: 'nextjs-contact',
});
return NextResponse.json({ ok: true });
} catch (err) {
console.error('Contact email failed:', err);
return NextResponse.json({ ok: false, error: 'Could not send your message.' }, { status: 502 });
}
}Server Action
The same thing with a Server Action and a form, without writing an API route:
// app/contact/actions.ts
'use server';
import { sendEmail } from '@/lib/securessmtp';
export type ContactState = { status: 'idle' | 'sent' | 'error'; message?: string };
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
export async function sendContact(_prev: ContactState, formData: FormData): Promise<ContactState> {
const name = String(formData.get('name') ?? '').trim();
const email = String(formData.get('email') ?? '').trim();
const message = String(formData.get('message') ?? '').trim();
if (!name || !EMAIL_RE.test(email) || !message) {
return { status: 'error', message: 'Please fill in every field.' };
}
try {
await sendEmail({
to: '[email protected]',
subject: `Contact form: ${name}`,
text: `Name: ${name}\nEmail: ${email}\n\n${message}`,
from: { name: 'Website contact form' },
reply_to: email,
});
return { status: 'sent', message: 'Thanks. Your message was sent.' };
} catch (err) {
console.error('Contact email failed:', err);
return { status: 'error', message: 'Could not send your message. Please try again later.' };
}
}useActionState is in React 19, which Next.js 15 uses. On Next.js 14 use useFormState from react-dom instead.
Handle errors
sendEmail throws a SecureSMTPError with the reason from the API. Log it on the server and show the visitor a general message.
| Response | Meaning | What to do |
|---|---|---|
200 ok: true | Accepted for sending. | Nothing. Keep message_id if you want to find the email later. |
200 rate_limited | More than 120 sends from this site in 60 seconds. | Wait a minute, then try again. |
200 over_quota | The account’s monthly email count is used up (relay mode only). | Wait for the new month, upgrade, or send from a verified domain. |
200 send_failed | The email was not sent. The error field says why. | Read error. Do not retry in a loop. |
400 invalid_payload, no_body, … | The request is wrong. details lists the fields for invalid_payload. | Fix the request. Retrying the same request fails the same way. |
400 content_flagged, ai_flagged | The content was judged to be spam. | Change the content. See Deliverability. |
401 missing_api_key, invalid_api_key | No key was sent, or the key is wrong or was rotated. | Check the environment variable on the server. |
403 site_disabled | The site is disabled. | See Blocks. |
- Check
okin the body, not only the HTTP status. Rate limit, monthly limit and send failures come back with HTTP 200. - There is no idempotency key. If a request times out, the email may already have been sent, and sending it again can deliver it twice.
- Every reason is listed in the error catalogue.
"ok": true, "held": true and the email is not sent. It shows as flagged in the email log.Or use SMTP
If you already send with Nodemailer, change its settings instead — see Node.js. Nodemailer needs the Node.js runtime, not the Edge runtime.
Next
- Let SecureSMTP run the form for you: Forms API.
- Every field and response: POST /mail/send.
- Send from your own address: Domains and DNS.