Security alert email: tell users when something sensitive changes

Updated

A security alert is the email a user reads at 2 a.m. and acts on. It earns trust when it is specific, calm and short, and it destroys trust when it looks like phishing.

When it fires

  • Sign-in from a new device or location
  • Password changed
  • Email address or phone number changed
  • Two-factor authentication turned off
  • API key created

Workflow

  1. The sensitive action completes.
  2. You record it in an audit log.
  3. You send the alert to the address on file before the change (important for email changes).
  4. The alert tells the user what happened, when, roughly where, and how to reach support or lock the account.

Implementation (Django)

import os
import requests
from django.conf import settings

def send_security_alert(user, event, details):
    resp = requests.post(
        "https://api.inboxili.com/api/v1/transactional/send",
        headers={"Authorization": f"Bearer {os.environ['INBOXILI_API_KEY']}"},
        json={
            "to": user.email,
            "from_email": "security@yourdomain.com",
            "from_name": "Acme Security",
            "subject": "Security alert: {{event}}",
            "html_body": (
                "<p>Hi {{name}},</p>"
                "<p>{{event}} on {{when}} (approximate location: {{place}}).</p>"
                "<p>If this was you, no action is needed. If not, secure your account at "
                "{{security_url}} and contact support.</p>"
            ),
            "text_body": f"{event} on {details['when']}. Secure your account: {settings.SITE_URL}/account/security",
            "template_data": {
                "name": user.first_name or "there",
                "event": event,
                "when": details["when"],
                "place": details.get("place", "unknown"),
                "security_url": f"{settings.SITE_URL}/account/security",
            },
        },
        timeout=10,
    )
    if not resp.ok:
        # Alerts are best effort. Never fail the user's action because of one.
        import logging
        logging.getLogger(__name__).warning("security alert failed: %s %s", resp.status_code, resp.text[:200])

Call it from a Django signal or from the view after the change commits, and send it from a background task so a slow API call cannot hold the request open.

Sample email

Subject: Security alert: New sign-in Hi Ada, New sign-in on 2026-10-07 14:03 UTC (approximate location: Berlin). If this was you, no action is needed. If not, secure your account.

Things that go wrong

  • Alerting the new address on an email change instead of the old one. The attacker would be the one notified.
  • Alert fatigue. Alert on new devices, not every login.
  • Links that act. One-click "this wasn't me" links are phishing targets and are easy to spoof. Link to a page that requires sign-in.
  • Sending only on success. Log failed alert sends so you know the control is working.

Delivery events

A bounced alert means the owner may not be getting notices at all. Flag the account for a stronger verification step.

Deliverability notes

Send from your security subdomain with SPF, DKIM and DMARC passing. Tell users in your docs that you never ask for passwords by email.

Frequently asked questions

Should alerts include a link?
Include one link to your account security page, typed from your own domain. Never include a link that logs the user in or changes settings directly.

Send a security alert

Create a workspace, verify a domain, and make your first API call.

Related