Password reset email template: HTML and plain text

A password reset email template in HTML and plain text, what to include, subject lines, a safe reset token flow and how to send it with Boundry.


A password reset email is a message you send when someone says they have forgotten their password. It contains a single-use link that lets them choose a new one, and it should say what it is for, how long the link lasts and what to do if they did not ask. This guide gives you a template in HTML and plain text, the subject lines that work, and the server-side flow that keeps the link safe.

Anyone who can open the link in this email can set a new password and take over the account, so the link works like a temporary password. The email matters less than what sits behind the link. Get the token and the page right first, then the wording.

What a password reset email must contain

  • A subject line that names the action, such as 'Reset your Acme password'. People search their inbox for it when they are locked out.
  • A sender name and domain the reader recognises, authenticated with SPF, DKIM and DMARC. A reset email that lands in spam is a support ticket.
  • One sentence saying a reset was requested for this address, and for which product.
  • One button or link to a page on your own domain. Put the plain URL under the button too, for clients that block buttons.
  • How long the link works. An hour is common; shorter is safer.
  • A line for the wrong person: 'If you did not ask for this, ignore this email. Your password has not changed.'
  • A way to get help, such as a reply address a person reads.
  • Nothing else. No promotions, no product news, no second call to action.

Password reset email template

This is a complete message to adapt. Replace Acme, the domain and the expiry with your own. Always send the plain-text part alongside the HTML one, so the link is visible in every client.

<p>Hi,</p>
<p>We got a request to reset the password for your Acme account.</p>
<p><a href="https://acme.com/reset?token=TOKEN">Choose a new password</a></p>
<p>This link works once and expires in 1 hour. If the button does not work, copy this address into your browser:</p>
<p>https://acme.com/reset?token=TOKEN</p>
<p>If you did not ask for this, ignore this email. Your password has not changed.</p>
<p>Need help? Reply to this email and a person will read it.</p>
Hi,

We got a request to reset the password for your Acme account.

Choose a new password: https://acme.com/reset?token=TOKEN

This link works once and expires in 1 hour. If you did not ask for this, ignore this email. Your password has not changed.

Need help? Reply to this email and a person will read it.

Password reset email subject lines

  • Reset your Acme password. The default, and the clearest.
  • Your Acme password reset link. Good when the reader is searching for the link later.
  • Reset your Acme password (link expires in 1 hour). Puts the deadline where it is seen without opening the email.
  • Avoid 'Action required', 'Urgent' and exclamation marks. They read like phishing, which is what a reset email gets mistaken for.

How the reset flow should work

The email is the visible part. The token flow is what decides whether someone else can take over an account. This follows the OWASP Forgot Password Cheat Sheet.

  • Take an email address on the 'forgot password' form and always show the same answer, such as 'If an account exists for that address, we have sent an email'. Do not reveal which addresses have accounts, and keep the response time the same either way.
  • Generate a long random token from a cryptographically secure source, at least 128 bits. Store only a hash of it, with the user and an expiry, the same way you would store a password.
  • Send the link to the address on file, never to an address supplied in the request.
  • Expire the token quickly, within the hour, and let it work once. Issuing a new token should invalidate the previous one.
  • When the link is opened, show a page that asks for the new password. Do not change anything on a plain GET, because some mail scanners open every link in a message. Check the token when the new password is submitted with a POST.
  • Serve the reset page with a Referrer-Policy of no-referrer, so the token in the URL is not passed to other sites in the Referer header.
  • After a successful reset, invalidate the token, end the user's other sessions, and send them through the normal sign-in flow.
  • Rate limit requests per address and per IP, so the form cannot be used to flood someone's inbox or to guess tokens.
  • Never put the old or a new password in the email.

Send the confirmation after the password changes

Once the password is changed, send a second email: 'Your Acme password was changed'. If the person did not do it, this is how they find out. Say when it changed, and give a way to lock the account and contact you. It is the same pattern as an account change confirmation.

Our confirmation emails guide has the subject lines and content for account change messages like this one. Confirmation emails, with examples

Send a password reset email with Boundry

With Boundry you send the reset email with one call to the emails API from the JavaScript, Python, Go, Ruby or PHP SDK, or with POST /emails directly. This example uses the JavaScript SDK, a domain you have verified in your project, and a token you have already created and stored as a hash.

import Boundry from "boundry-sdk";

const client = new Boundry({
  apiKey: process.env["BOUNDRY_API_KEY"],
});

const link = `https://acme.com/reset?token=${token}`;

await client.emails.send({
  from: "Acme <accounts@mail.acme.com>",
  to: user.email,
  subject: "Reset your Acme password",
  html: `<p>We got a request to reset the password for your Acme account.</p>
<p><a href="${link}">Choose a new password</a></p>
<p>This link works once and expires in 1 hour. If you did not ask for this, ignore this email. Your password has not changed.</p>`,
  text: `We got a request to reset the password for your Acme account.\n\nChoose a new password: ${link}\n\nThis link works once and expires in 1 hour. If you did not ask for this, ignore this email. Your password has not changed.`,
});

Boundry projects are bound to one region, and the Sydney region (ap-southeast-2) is the one live today, so the message and its delivery record are stored there. A reset email carries a person's address and a credential-bearing link, so where it is stored is worth knowing.

Send it from your server as soon as the request arrives. After sending, Boundry reports the outcome as events: email.delivered when the recipient's server accepts the message and email.bounced when it does not. Subscribe a signed webhook to those events, and record bounces privately. Keep the public reset page identical whether or not the email bounced, because bounce feedback there would reveal which addresses have accounts. Show bounce details only after the person has signed in or proved they own the address.

The sending documentation lists every field the emails API accepts. Read the sending docs

Why a password reset email does not arrive

  • The address is not the one on the account. Because the form gives the same answer for every address, the person cannot tell. Say on the page which address you tried, and mention checking other addresses they might have used.
  • It went to spam. Authenticate your sending domain and keep the message short and plain. See the guide on why emails go to spam.
  • It bounced. Check the bounce class and code before doing anything. See the email bounces guide.
  • The link was opened by a scanner. If your reset page changes anything on a GET request, a mail scanner can use up the token before the person clicks. Require a POST.
  • The token expired or was replaced by a newer request. Say so on the page, and offer to send a new link.

Authentication, reputation and content checks are covered in the guide to why emails go to spam. Why your emails go to spam

Bounce codes and what to do about each are in the email bounces guide. Email bounces, explained

Password resets are one of the core transactional email types. The overview explains the rest. What is transactional email?

Replies to a reset email are often real questions. Decide where they go before you send from a no-reply address. No-reply email addresses

Skipping passwords altogether? A magic link uses the same token rules and a shorter expiry. Magic links: how passwordless sign-in emails work

Frequently asked questions

What should a password reset email say?

Say that a reset was requested for the address, give one link to choose a new password, state how long the link lasts, and tell anyone who did not ask that they can ignore the email and their password has not changed.

How long should a password reset link last?

Keep it short. An hour is common, and shorter is safer. The link should work once, and requesting a new one should invalidate the old one.

Should a password reset email include the password?

No. Never email a password, old or new. Send a single-use link to a page where the person chooses a new one.

Why does the reset form say 'if an account exists' for every address?

So the form does not reveal which email addresses have accounts. It gives the same message whether or not the address is registered, and it should respond in about the same time.

Is a password reset email a transactional email?

Yes. The recipient triggers it and expects it within seconds, which is what makes an email transactional. Keep promotional content out of it.

Why is my password reset email not arriving?

The usual causes are a different address from the one on the account, a sending domain without SPF, DKIM and DMARC, spam filtering, a bounce from the recipient's server, or a mail scanner using up the link. Use the delivery events to investigate bounces and other delivery failures, ask the recipient to check their spam folder, and check your own records for whether the token expired or was already used.

How do I send a password reset email with Boundry?

Call the emails API (POST /emails, or emails.send in an official SDK) from your server with a verified sending domain, the recipient, a subject, and an HTML and plain-text body containing your single-use reset link.

If you want to see what I mean, send something real through Sydney. The Sandbox plan includes 3,000 emails a month. I'd like to hear how it goes.

MitchFounder, Boundry

Create a project when you're ready →