Skip to content
Docs menu

Guides

Features

API

Integrations

Express

Send a password reset email from an Express route with the wakio package.

npm install wakio express-rate-limit
import { createHash } from "node:crypto"
import express from "express"
import { rateLimit } from "express-rate-limit"
import { Wakio } from "wakio"

const app = express()
const wakio = new Wakio()

app.use(express.json())

const resetLimit = rateLimit({ windowMs: 15 * 60 * 1000, limit: 5 })

app.post("/password-reset", resetLimit, (req, res) => {
  const email = typeof req.body?.email === "string" ? req.body.email : ""
  sendPasswordReset(email).catch((error) => console.error("password reset email failed", error))
  res.sendStatus(202)
})

async function sendPasswordReset(email: string) {
  const user = await findUserByEmail(email)
  if (!user) return

  const token = await createResetToken(user)
  const tokenHash = createHash("sha256").update(token).digest("hex")
  await wakio.emails.send(
    { to: user.email, template: "password-reset", mergeData: { link: `https://acme.com/reset/${token}` } },
    { idempotencyKey: `password-reset-${tokenHash}` },
  )
}

Set WAKIO_API_KEY in your environment. A few details in this route matter:

  • The email goes to user.email, the address on the account, not to whatever the request sent. A request body can carry a list of addresses where you expected one.
  • The route allows each IP address 5 requests every 15 minutes. Each request emails a real person, so an unthrottled route lets anyone flood someone's inbox from your domain.
  • The route answers 202 at once, before looking the address up, so neither the answer nor how long it takes reveals who has an account. In production, put sendPasswordReset on your job queue instead, so a restart cannot lose it.
  • The idempotency key is a hash of the token, so a retry of this send cannot deliver the email twice, and the token is kept out of the key, which everyone on your Wakio account can see.

For the full client, see Node.js.