Transactionele e-mails in Next.js
Verstuur welkomstmails, bestelbevestigingen en wachtwoordresets rechtstreeks vanuit Next.js — via Server Actions of API Routes, zonder extra bibliotheek.
Vereisten
- Een Wesender-account met een geverifieerd domein
- Next.js 13 of hoger (App Router)
- Je API-key uit het dashboard (Instellingen → API-sleutels)
Stap 1: API-key instellen
Voeg je API-key toe aan .env.local (nooit committen naar git):
WESENDER_API_KEY=ws_live_xxxxxxxxxxxxxxxx
Belangrijk: Gebruik geen
NEXT_PUBLIC_-prefix. De API-key mag nooit in de browser terechtkomen.
Stap 2: Email helper aanmaken
Maak lib/email.ts aan als centrale helper voor alle e-mailverzending:
type SendEmailOptions = {
to: string | string[]
subject: string
html: string
text?: string
from?: string
}
export async function sendEmail({ to, subject, html, text, from }: SendEmailOptions) {
const res = await fetch("https://api.wesender.nl/emails", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.WESENDER_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
from: from ?? "noreply@mijndomein.nl",
to: Array.isArray(to) ? to : [to],
subject,
html,
text,
}),
})
if (!res.ok) {
const err = await res.json().catch(() => ({}))
throw new Error(`E-mail versturen mislukt: ${err.message ?? res.statusText}`)
}
return res.json() as Promise<{ id: string }>
}
Stap 3: Welkomstmail na registratie
// app/actions/auth.ts
"use server"
import { sendEmail } from "@/lib/email"
export async function registerUser(email: string, name: string) {
// ... registratie logica ...
await sendEmail({
to: email,
subject: `Welkom, ${name}!`,
html: `
<h2>Welkom bij ons platform, ${name}!</h2>
<p>Je account is succesvol aangemaakt.</p>
<p><a href="https://app.mijndomein.nl">Ga naar het dashboard</a></p>
`,
})
}
Stap 4: Wachtwoord-reset e-mail
// app/actions/reset-password.ts
"use server"
import { sendEmail } from "@/lib/email"
import { randomBytes } from "crypto"
export async function requestPasswordReset(email: string) {
const token = randomBytes(32).toString("hex")
// Sla token op in database met vervaltijd...
const resetLink = `https://app.mijndomein.nl/reset?token=${token}`
await sendEmail({
to: email,
subject: "Wachtwoord opnieuw instellen",
html: `
<p>Je hebt een wachtwoordreset aangevraagd.</p>
<p><a href="${resetLink}">Klik hier om je wachtwoord in te stellen</a></p>
<p><small>Deze link is 1 uur geldig. Heb je dit niet aangevraagd? Negeer deze e-mail.</small></p>
`,
})
}
Stap 5: Bestelbevestiging
// app/actions/checkout.ts
"use server"
import { sendEmail } from "@/lib/email"
export async function sendOrderConfirmation(email: string, orderId: string, total: number) {
await sendEmail({
to: email,
subject: `Bestelling #${orderId} bevestigd`,
html: `
<h2>Bedankt voor je bestelling!</h2>
<p>Ordernummer: <strong>#${orderId}</strong></p>
<p>Totaalbedrag: <strong>€${total.toFixed(2)}</strong></p>
<p>Je ontvangt een aparte e-mail zodra je bestelling is verzonden.</p>
`,
})
}
Tips
- Verstuur e-mails altijd server-side — nooit vanuit client components of de browser
- Gebruik een achtergrondwachtrij (BullMQ, Inngest, Trigger.dev) voor hoge volumes
- Voeg altijd een plain-text versie toe via de
text-parameter voor betere deliverability - Test lokaal door de API-respons te loggen in de server console
Foutoplossing
- 401 Unauthorized — controleer of
WESENDER_API_KEYcorrect is ingesteld in.env.local - 422 Unprocessable Entity — het
from-domein is niet geverifieerd in je Wesender-dashboard - E-mail niet ontvangen — controleer de spam-map en de logboeken in het Wesender-dashboard