Transactional emails via Resend for password resets, invitations, and welcome messages.
Features
- Password Reset - Secure password recovery flow
- User Invitations - Invite users with role and organization context
- Welcome Emails - Onboarding emails after account creation
- Beautiful Templates - Responsive HTML templates with your branding
Setup
1. Create Resend Account
- Sign up at resend.com
- Add and verify your domain in Domains section
- Add the DNS records (SPF, DKIM, optionally DMARC)
đĄ
DNS Propagation
DNS changes usually complete within a few hours, but can take up to 48 hours.
2. Generate API Key
- Go to API Keys in Resend dashboard
- Click Create API Key
- Copy the key (you won't see it again)
3. Configure Environment
Add to backend/.env:
RESEND_API_KEY=re_your_api_key_here
EMAIL_FROM_ADDRESS=noreply@yourdomain.com
EMAIL_FROM_NAME=Your App Name
| Variable | Description | Required |
|---|---|---|
RESEND_API_KEY |
Your Resend API key | Yes |
EMAIL_FROM_ADDRESS |
Sender email (must be from verified domain) | Yes |
EMAIL_FROM_NAME |
Display name in email clients | No |
â ī¸
Domain Verification Required
Emails will fail if EMAIL_FROM_ADDRESS uses an unverified domain.
Email Functions
All functions are in app/controllers/email.py:
send_email
Base function for sending any email:
from app.controllers.email import send_email
await send_email(
to="user@example.com",
subject="Hello!",
html_body="<h1>Welcome</h1>"
)
send_password_reset_email
from app.controllers.email import send_password_reset_email
await send_password_reset_email(
to="user@example.com",
reset_url="https://yourapp.com/reset-password?token=abc123"
)
send_invitation_email
from app.controllers.email import send_invitation_email
await send_invitation_email(
to="newuser@example.com",
invite_url="https://yourapp.com/accept-invite?token=xyz789",
invited_by="Admin User",
organization_name="Acme Corp", # Optional
role_name="Editor" # Optional
)
send_welcome_email
from app.controllers.email import send_welcome_email
await send_welcome_email(
to="user@example.com",
first_name="John", # Optional
organization_name="Acme Corp" # Optional
)
Email Templates
All emails use responsive HTML with:
- Clean, professional styling
- Your app name from
APP_NAMEsetting - Gradient header with branding
- Call-to-action buttons
| Email Type | Subject | Expiry Config |
|---|---|---|
| Password Reset | "Reset your {APP_NAME} password" | AUTH_PASSWORD_RESET_EXPIRE_HOURS (default: 24) |
| Invitation | "You're invited to join {ORG} on {APP_NAME}" | AUTH_INVITATION_EXPIRE_DAYS (default: 7) |
| Welcome | "Welcome to {APP_NAME}!" | N/A |
Configuration
Optional settings in backend/.env:
# Password reset link validity (hours)
AUTH_PASSWORD_RESET_EXPIRE_HOURS=24
# Invitation link validity (days)
AUTH_INVITATION_EXPIRE_DAYS=7
Testing Locally
For development without domain verification:
- Use your Resend API key (test keys work)
- Set
EMAIL_FROM_ADDRESS=onboarding@resend.dev - Emails deliver to real addresses
âšī¸
Resend Free Tier
3,000 emails/month and 100 emails/day - plenty for development and small apps.
Customizing Templates
Edit the HTML in app/controllers/email.py. Each function contains its template:
async def send_password_reset_email(to: str, reset_url: str) -> bool:
subject = f"Reset your {settings.app_name} password"
html_body = f"""
<!DOCTYPE html>
<html>
<body>
<h1>Reset Your Password</h1>
<a href="{reset_url}">Reset Password</a>
</body>
</html>
"""
return await send_email(to, subject, html_body)
Troubleshooting
Emails Not Sending
Check configuration:
from app.core.config import settings print(settings.email_is_configured) # Should be TrueVerify domain in Resend dashboard - all DNS records should show green checkmarks
Check logs:
docker compose logs backend | grep -i email
Emails Going to Spam
- Ensure DKIM and SPF records are configured
- Add a DMARC record
- Use a professional "from" address (avoid
no-reply@)
Next Steps
- Authentication - Password reset and invitation flows
- Backend Architecture - Module pattern