🚧 FastCMS is under active development — not ready for production use. APIs and features may change without notice.
FastCMS
Advanced

Email

Configure SMTP for transactional emails, security notifications, and passwordless login. Test your setup from the Admin UI.

Email

FastCMS sends transactional emails (verification, password reset, OTP) and security alert emails via any standard SMTP provider. Configuration is done through environment variables.


Configuration

Set these variables in your .env file:

# SMTP server
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=you@gmail.com
SMTP_PASSWORD=your-app-password

# Sender identity
SMTP_FROM_EMAIL=noreply@yourapp.com
SMTP_FROM_NAME=YourApp

# Security notifications (see below)
SECURITY_NOTIFICATIONS_ENABLED=true
SECURITY_LOGIN_NOTIFICATIONS=false

Email is disabled by default. FastCMS checks for SMTP_USER and SMTP_PASSWORD — if either is empty, no email will be sent and all operations that require email will either fail gracefully or be skipped.

Provider examples

Gmail (App Password):

SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=you@gmail.com
SMTP_PASSWORD=xxxx-xxxx-xxxx-xxxx   # App-specific password, not your account password

Amazon SES:

SMTP_HOST=email-smtp.us-east-1.amazonaws.com
SMTP_PORT=587
SMTP_USER=AKIAIOSFODNN7EXAMPLE
SMTP_PASSWORD=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

Resend / SendGrid / Mailgun:

SMTP_HOST=smtp.resend.com       # or smtp.sendgrid.net / smtp.mailgun.org
SMTP_PORT=587
SMTP_USER=resend                 # provider-specific
SMTP_PASSWORD=re_xxxxxxxxxxxxx  # API key used as password

Transactional Emails

FastCMS automatically sends the following emails when SMTP is configured:

EventTriggerExpiry
Email verificationUser registers24 hours
Password resetPOST /api/v1/auth/request-password-reset1 hour
Email change confirmationPOST /api/v1/auth/request-email-change24 hours
OTP (passwordless login)POST /api/v1/auth/request-otp10 minutes

All email links include BASE_URL as the base, so set it correctly in production:

BASE_URL=https://yourdomain.com

Security Notifications

When SECURITY_NOTIFICATIONS_ENABLED=true, FastCMS emails users on security-relevant events:

NotificationWhen sentDefault
Password changedUser changes or resets their password✓ On
2FA enabledTwo-factor authentication turned on✓ On
2FA disabledTwo-factor authentication turned off✓ On
Account lockedToo many failed login attempts✓ On
New loginEvery successful sign-inOff (opt-in)

Enable the login alert:

SECURITY_LOGIN_NOTIFICATIONS=true

Login notifications are off by default because they fire on every successful login — suitable for high-security apps but noisy for consumer apps.


Admin UI

Go to Admin → Email (/admin/settings/email) to:

  • View your current SMTP configuration (password masked)
  • See which security notifications are active
  • Send a test email to verify your setup

Send a test email via API

POST /api/v1/admin/email/test
Authorization: Bearer ADMIN_TOKEN
Content-Type: application/json

{"to_email": "you@example.com"}

Response:

{"success": true, "message": "Test email sent to you@example.com"}

Check SMTP status:

GET /api/v1/admin/email/status
Authorization: Bearer ADMIN_TOKEN
{
  "smtp_enabled": true,
  "smtp_host": "smtp.gmail.com",
  "smtp_port": 587,
  "smtp_user": "you***ail.com",
  "smtp_from_email": "noreply@yourapp.com",
  "smtp_from_name": "YourApp",
  "security_notifications_enabled": true,
  "security_login_notifications": false
}

Email Templates

All emails use a consistent branded template with:

  • Your APP_NAME in the header
  • HTML + plain-text parts (for accessibility and spam filter compatibility)
  • Call-to-action button with a fallback link
  • Branded footer

Sending custom emails from a plugin

# plugins/my_plugin/hooks.py
from app.services.email_service import EmailService, _html_wrap, _plain_text

async def notify_user(user_email: str, order_id: str) -> None:
    html = _html_wrap(
        title="Your order is confirmed!",
        body=f"Order <strong>{order_id}</strong> has been placed successfully.",
        cta_url=f"https://yourapp.com/orders/{order_id}",
        cta_label="View Order",
    )
    plain = _plain_text(
        "Your order is confirmed!",
        f"Order {order_id} has been placed.",
        f"https://yourapp.com/orders/{order_id}",
    )
    await EmailService.send_email(
        to_email=user_email,
        subject=f"Order {order_id} confirmed",
        html_content=html,
        plain_content=plain,
    )

Troubleshooting

ProblemSolution
Emails not sent, no errorSMTP_USER or SMTP_PASSWORD is empty — email is silently skipped
Connection refusedWrong SMTP_HOST or SMTP_PORT — try port 465 for SSL
Authentication failedWrong credentials. For Gmail, use an App Password, not your account password
Links in emails go to wrong domainSet BASE_URL to your production domain
Test email succeeds but real emails don'tCheck that SMTP_FROM_EMAIL is a verified sender in your provider
Emails end up in spamSet up SPF, DKIM, and DMARC records for your sending domain

Notes

  • SMTP uses STARTTLS on port 587 (the most compatible mode). SSL on port 465 is not currently supported — use an SMTP relay that supports STARTTLS.
  • All emails are sent asynchronously — SMTP runs in a thread-pool executor so it never blocks the request cycle.
  • Security notification emails fire as asyncio.create_task (fire-and-forget) — a delivery failure won't affect the user-facing response.

On this page