emailr_
All articles
usecase·9 min

Account security alert emails: Patterns and copy

securityalertspatterns

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

  1. Send immediately - Security alerts must arrive in real-time
  2. Clear subject lines - User should know the issue from subject alone
  3. Include context - Time, location, device information
  4. Provide actions - Clear next steps for both scenarios
  5. Don't include sensitive data - No passwords or full tokens in emails
  6. Use consistent branding - So users recognize legitimate alerts
  7. 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.

e_

Written by the emailr team

Building email infrastructure for developers

Ready to start sending?

Get your API key and send your first email in under 5 minutes. No credit card required.