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_KEY correct 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