Tutorial•3 min read

Secure Password Reset Emails Developers Should Ship

Learn how to implement secure password reset emails using one-time tokens, time-limited links, and phishing-resistant content. Enhance your account security with robust transactional email practices.

Secure Password Reset Emails Developers Should Ship

Section 1

The Critical Role of Secure Password Resets

Password reset functionality is a cornerstone of account security, yet it's often a prime target for attackers. An insecure password reset email flow can expose user accounts to hijacking, leading to significant data breaches. Developers must prioritize robust implementations that protect users and maintain trust in their application's security posture.

Section 2

A fundamental security measure for password reset links is to ensure they are single-use and expire after a short duration. This prevents replay attacks where an attacker might try to reuse an intercepted link. Generate a cryptographically secure, one-time token for each request and associate it with the user, setting a strict token expiry (TTL) of typically 15-60 minutes.

python
1import requests
2import os
3import secrets
4import time
5
6# Assume user_id and email are retrieved from your database
7user_id = "user_123"
8user_email = "user@example.com"
9reset_token = secrets.token_urlsafe(32) # Generate a secure, one-time token
10expires_at = int(time.time()) + 3600 # Link expires in 1 hour (3600 seconds)
11
12# Store reset_token and expires_at in your database associated with user_id
13
14reset_link = f"https://your-app.com/reset-password?token={reset_token}&user={user_id}"
15
16PIISEND_API_KEY = os.getenv("PIISEND_API_KEY")
17if not PIISEND_API_KEY:
18 raise ValueError("PIISEND_API_KEY environment variable not set")
19
20headers = {
21 "Authorization": f"Bearer {PIISEND_API_KEY}",
22 "Content-Type": "application/json"
23}
24
25payload = {
26 "from": "noreply@your-verified-domain.com",
27 "to": user_email,
28 "subject": "Reset Your Password",
29 "html": f"<h1>Password Reset Request</h1><p>Hello,</p><p>You recently requested to reset your password for your account. Click the link below to proceed:</p><p><a href=\"{reset_link}\">Reset Password</a></p><p>This link will expire in 1 hour. If you did not request a password reset, please ignore this email.</p><p>Thanks,<br>Your App Team</p>",
30 "text": f"Hello,\nYou recently requested to reset your password for your account. Click the link below to proceed:\n{reset_link}\nThis link will expire in 1 hour. If you did not request a password reset, please ignore this email.\nThanks,\nYour App Team",
31 "metadata": {
32 "user_id": user_id,
33 "reset_token_id": reset_token # For logging/tracking
34 }
35}
36
37try:
38 response = requests.post("https://api.piisend.com/api/v1/emails", headers=headers, json=payload)
39 response.raise_for_status() # Raise an exception for HTTP errors
40 print("Password reset email sent successfully!")
41 print(response.json())
42except requests.exceptions.RequestException as e:
43 print(f"Error sending email: {e}")
44 if response is not None:
45 print(response.text)

Section 3

Designing Phishing-Resistant Email Content

Beyond technical security, the content of your password reset email plays a crucial role in preventing phishing attacks. Always use clear, consistent branding, including your company logo and name. Explicitly state what the email is for and, more importantly, what it is not for. Instruct users never to share the link and to report suspicious activity. Use merge variables to personalize the email without revealing sensitive information.

bash
1curl -X POST https://api.piisend.com/api/v1/emails \
2 -H "Authorization: Bearer YOUR_API_KEY" \
3 -H "Content-Type: application/json" \
4 -d '{
5 "from": "security@your-verified-domain.com",
6 "to": "recipient@example.com",
7 "subject": "Action Required: Reset Your Password for [Your App Name]",
8 "html": "<html><body><h1>Reset Your Password</h1><p>Hello {{name}},</p><p>We received a request to reset the password for your account associated with {{email_address}}.</p><p>To complete the process, please click the secure link below:</p><p><a href=\"{{reset_link}}\">Reset My Password Now</a></p><p>This link is valid for the next 30 minutes. If you did not request a password reset, please disregard this email. Your account security is important to us.</p><p>Thanks,<br>The {{app_name}} Team</p></body></html>",
9 "text": "Hello {{name}},\nWe received a request to reset the password for your account associated with {{email_address}}.\nTo complete the process, please visit: {{reset_link}}\nThis link is valid for the next 30 minutes. If you did not request a password reset, please disregard this email. Your account security is important to us.\nThanks,\nThe {{app_name}} Team",
10 "variables": {
11 "name": "John Doe",
12 "email_address": "recipient@example.com",
13 "reset_link": "https://your-app.com/reset?token=SECURE_RESET_TOKEN_HERE",
14 "app_name": "Your App"
15 },
16 "metadata": {
17 "transaction_type": "password_reset",
18 "user_id": "user_abc"
19 }
20 }'

Section 4

Monitor Delivery and Security Events with Webhooks

After sending a password reset email, it's crucial to monitor its delivery status. Piisend's webhooks provide real-time notifications for events like delivered, bounced, or opened emails. By integrating these webhooks, you can detect potential issues, such as emails bouncing to non-existent addresses (indicating a possible attack attempt) or users not receiving the email, allowing for proactive support and enhanced account security.

Section 5

Preventing Duplicate Resets with Idempotency

In high-traffic applications, users might click the password reset button multiple times, leading to multiple emails being sent. Piisend's idempotent email sends allow you to prevent duplicate emails for the same logical request. By including an Idempotency-Key header, you ensure that even if the request is sent multiple times, only one email is processed and delivered.

Start sending

Ship transactional email in minutes

Create an API key, verify a domain, and send your first message with Piisend.