Security alert emails are critical for protecting user accounts. They must be clear, actionable, and arrive instantly. Here's how to design effective security notifications.
Types of security alerts
Login notifications
interface LoginAlert {
user: { email: string; name: string };
login: {
timestamp: Date;
ip: string;
location?: { city: string; country: string };
device: string;
browser: string;
};
isNewDevice: boolean;
isNewLocation: boolean;
}
await sendEmail({
to: user.email,
subject: login.isNewDevice
? 'New device signed in to your account'
: 'New sign-in to your account',
template: 'login-alert',
data: {
user,
login,
secureAccountUrl: `${baseUrl}/security`,
notYouUrl: `${baseUrl}/security/report?session=${sessionId}`
},
priority: 'high'
});
Password change confirmation
await sendEmail({
to: user.email,
subject: 'Your password was changed',
template: 'password-changed',
data: {
user,
changedAt: new Date(),
device: request.device,
location: request.location,
resetUrl: `${baseUrl}/reset-password`,
supportEmail: '[email protected]'
}
});
Suspicious activity warning
interface SuspiciousActivityAlert {
user: User;
activity: {
type: 'failed_logins' | 'unusual_location' | 'api_abuse' | 'data_export';
details: string;
timestamp: Date;
riskLevel: 'low' | 'medium' | 'high';
};
recommendedActions: string[];
}
await sendEmail({
to: user.email,
subject: '⚠️ Unusual activity detected on your account',
template: 'suspicious-activity',
data: {
user,
activity,
recommendedActions: [
'Change your password',
'Review recent activity',
'Enable two-factor authentication'
],
securityDashboardUrl: `${baseUrl}/security`,
lockAccountUrl: `${baseUrl}/security/lock?token=${lockToken}`
},
priority: 'high'
});
Two-factor authentication emails
2FA enabled confirmation
await sendEmail({
to: user.email,
subject: 'Two-factor authentication enabled',
template: '2fa-enabled',
data: {
user,
method: '2fa.method', // 'authenticator' | 'sms' | 'email'
backupCodes: user.hasBackupCodes,
manageUrl: `${baseUrl}/security/2fa`
}
});
Backup codes regenerated
await sendEmail({
to: user.email,
subject: 'New backup codes generated',
template: 'backup-codes-regenerated',
data: {
user,
generatedAt: new Date(),
device: request.device,
warning: 'Your old backup codes no longer work',
viewCodesUrl: `${baseUrl}/security/backup-codes`
}
});
Session and device management
New device added
await sendEmail({
to: user.email,
subject: 'New device added to your account',
template: 'new-device',
data: {
user,
device: {
name: deviceInfo.name,
type: deviceInfo.type,
browser: deviceInfo.browser,
os: deviceInfo.os,
addedAt: new Date()
},
location: deviceInfo.location,
removeDeviceUrl: `${baseUrl}/security/devices/${deviceId}/remove`,
viewAllDevicesUrl: `${baseUrl}/security/devices`
}
});
All sessions terminated
await sendEmail({
to: user.email,
subject: 'All sessions signed out',
template: 'sessions-terminated',
data: {
user,
terminatedAt: new Date(),
terminatedBy: 'you', // or 'admin' or 'security_system'
sessionsCount: terminatedCount,
reason: 'Password change', // or 'Security concern' or 'User request'
signInUrl: `${baseUrl}/login`
}
});
API and access token alerts
API key created
await sendEmail({
to: user.email,
subject: 'New API key created',
template: 'api-key-created',
data: {
user,
apiKey: {
name: key.name,
prefix: key.prefix, // First 8 chars only
permissions: key.permissions,
createdAt: new Date()
},
manageKeysUrl: `${baseUrl}/settings/api-keys`
}
});
Unusual API activity
await sendEmail({
to: user.email,
subject: 'Unusual API activity detected',
template: 'api-activity-alert',
data: {
user,
activity: {
keyName: key.name,
requestCount: activity.count,
timeWindow: '1 hour',
normalRange: '100-500 requests',
actualCount: activity.count
},
revokeKeyUrl: `${baseUrl}/settings/api-keys/${key.id}/revoke`,
viewLogsUrl: `${baseUrl}/settings/api-keys/${key.id}/logs`
}
});
Email change flow
Email change requested
// Send to OLD email
await sendEmail({
to: user.currentEmail,
subject: 'Email change requested',
template: 'email-change-requested',
data: {
user,
newEmail: maskEmail(newEmail),
requestedAt: new Date(),
cancelUrl: `${baseUrl}/security/email-change/cancel?token=${cancelToken}`,
expiresIn: '24 hours'
}
});
// Send to NEW email
await sendEmail({
to: newEmail,
subject: 'Verify your new email address',
template: 'email-change-verify',
data: {
user,
verifyUrl: `${baseUrl}/security/email-change/verify?token=${verifyToken}`,
expiresIn: '24 hours'
}
});
Security alert best practices
Priority and timing
const securityEmailConfig = {
// Immediate, high priority
immediate: [
'password_changed',
'email_changed',
'suspicious_activity',
'2fa_disabled',
'all_sessions_terminated'
],
// Can batch or slight delay
batchable: [
'new_login_known_device',
'api_key_created',
'settings_changed'
]
};
async function sendSecurityAlert(type: string, data: any) {
const isImmediate = securityEmailConfig.immediate.includes(type);
await sendEmail({
...data,
priority: isImmediate ? 'high' : 'normal',
headers: {
'X-Priority': isImmediate ? '1' : '3',
'X-Security-Alert': 'true'
}
});
}
Clear action items
// Always include:
// 1. What happened
// 2. When it happened
// 3. What to do if it was you
// 4. What to do if it wasn't you
const securityEmailStructure = {
whatHappened: 'Your password was changed',
when: formatDateTime(event.timestamp),
where: `${event.location.city}, ${event.location.country}`,
device: event.device,
ifYou: 'No action needed. You can ignore this email.',
ifNotYou: {
actions: [
{ label: 'Reset your password', url: resetUrl },
{ label: 'Contact support', url: supportUrl }
],
urgency: 'Do this immediately to secure your account'
}
};
Best practices
- —Send immediately - Security alerts must arrive in real-time
- —Clear subject lines - User should know the issue from subject alone
- —Include context - Time, location, device information
- —Provide actions - Clear next steps for both scenarios
- —Don't include sensitive data - No passwords or full tokens in emails
- —Use consistent branding - So users recognize legitimate alerts
- —Test deliverability - Security emails must reach inbox, not spam
Security alerts build trust. When users know you're watching out for them, they feel safer using your product.