xiaohongshu
xiaohongshu
email-integration
Implement email functionality using FastAPI-Mail for password reset, notifications, and transactional emails. Use when setting up email services or notification systems.
Full skill instructions
You implement email functionality for the QA Team Portal using FastAPI-Mail.
cd backend
uv pip install fastapi-mail python-multipart jinja2
Location: backend/app/core/config.py
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
# Email Configuration
MAIL_USERNAME: str
MAIL_PASSWORD: str
MAIL_FROM: str
MAIL_PORT: int = 587
MAIL_SERVER: str
MAIL_FROM_NAME: str = "QA Team Portal"
MAIL_TLS: bool = True
MAIL_SSL: bool = False
USE_CREDENTIALS: bool = True
VALIDATE_CERTS: bool = True
class Config:
env_file = ".env"
settings = Settings()
Environment Variables (.env):
# Email Configuration
[email protected]
MAIL_PASSWORD=your-smtp-password
[email protected]
MAIL_PORT=587
MAIL_SERVER=smtp.gmail.com
MAIL_FROM_NAME=QA Team Portal
MAIL_TLS=true
MAIL_SSL=false
Location: backend/app/services/email_service.py
from fastapi_mail import FastMail, MessageSchema, ConnectionConfig, MessageType
from fastapi import BackgroundTasks
from pathlib import Path
from typing import List, Dict, Any
from app.core.config import settings
from jinja2 import Environment, FileSystemLoader
import logging
logger = logging.getLogger(__name__)
# Email configuration
conf = ConnectionConfig(
MAIL_USERNAME=settings.MAIL_USERNAME,
MAIL_PASSWORD=settings.MAIL_PASSWORD,
MAIL_FROM=settings.MAIL_FROM,
MAIL_PORT=settings.MAIL_PORT,
MAIL_SERVER=settings.MAIL_SERVER,
MAIL_FROM_NAME=settings.MAIL_FROM_NAME,
MAIL_STARTTLS=settings.MAIL_TLS,
MAIL_SSL_TLS=settings.MAIL_SSL,
USE_CREDENTIALS=settings.USE_CREDENTIALS,
VALIDATE_CERTS=settings.VALIDATE_CERTS,
TEMPLATE_FOLDER=Path(__file__).parent.parent / 'templates' / 'email'
)
fm = FastMail(conf)
class EmailService:
"""Service for sending emails."""
@staticmethod
async def send_email(
recipients: List[str],
subject: str,
body: str,
html: str = None,
background_tasks: BackgroundTasks = None
):
"""
Send email to recipients.
Args:
recipients: List of recipient email addresses
subject: Email subject
body: Plain text body
html: HTML body (optional)
background_tasks: FastAPI background tasks
"""
message = MessageSchema(
subject=subject,
recipients=recipients,
body=body,
subtype=MessageType.html if html else MessageType.plain
)
try:
if background_tasks:
background_tasks.add_task(fm.send_message, message)
else:
await fm.send_message(message)
logger.info(f"Email sent to {', '.join(recipients)}: {subject}")
return True
except Exception as e:
logger.error(f"Failed to send email: {str(e)}")
return False
@staticmethod
async def send_template_email(
recipients: List[str],
subject: str,
template_name: str,
template_data: Dict[str, Any],
background_tasks: BackgroundTasks = None
):
"""
Send email using Jinja2 template.
Args:
recipients: List of recipient email addresses
subject: Email subject
template_name: Template filename (e.g., 'password_reset.html')
template_data: Data to pass to template
background_tasks: FastAPI background tasks
"""
message = MessageSchema(
subject=subject,
recipients=recipients,
template_body=template_data,
subtype=MessageType.html
)
try:
if background_tasks:
background_tasks.add_task(
fm.send_message,
message,
template_name=template_name
)
else:
await fm.send_message(message, template_name=template_name)
logger.info(f"Template email sent to {', '.join(recipients)}: {subject}")
return True
except Exception as e:
logger.error(f"Failed to send template email: {str(e)}")
return False
@staticmethod
async def send_password_reset_email(
email: str,
reset_token: str,
background_tasks: BackgroundTasks = None
):
"""Send password reset email with link."""
reset_url = f"{settings.FRONTEND_URL}/reset-password?token={reset_token}"
template_data = {
"reset_url": reset_url,
"expiry_minutes": 15,
"support_email": settings.MAIL_FROM
}
return await EmailService.send_template_email(
recipients=[email],
subject="Password Reset Request",
template_name="password_reset.html",
template_data=template_data,
background_tasks=background_tasks
)
@staticmethod
async def send_welcome_email(
email: str,
name: str,
background_tasks: BackgroundTasks = None
):
"""Send welcome email to new user."""
template_data = {
"name": name,
"login_url": f"{settings.FRONTEND_URL}/admin/login",
"support_email": settings.MAIL_FROM
}
return await EmailService.send_template_email(
recipients=[email],
subject="Welcome to QA Team Portal",
template_name="welcome.html",
template_data=template_data,
background_tasks=background_tasks
)
@staticmethod
async def send_account_locked_email(
email: str,
name: str,
background_tasks: BackgroundTasks = None
):
"""Send email when account is locked."""
template_data = {
"name": name,
"unlock_time": "30 minutes",
"support_email": settings.MAIL_FROM
}
return await EmailService.send_template_email(
recipients=[email],
subject="Account Locked - QA Team Portal",
template_name="account_locked.html",
template_data=template_data,
background_tasks=background_tasks
)
@staticmethod
async def send_test_email(recipient: str):
"""Send test email to verify configuration."""
return await EmailService.send_email(
recipients=[recipient],
subject="Test Email - QA Team Portal",
body="This is a test email from QA Team Portal. Email configuration is working correctly!"
)
Location: backend/app/templates/email/base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ subject }}</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #333;
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.header {
background: linear-gradient(135deg, #0066CC 0%, #0052A3 100%);
color: white;
padding: 30px;
text-align: center;
border-radius: 8px 8px 0 0;
}
.content {
background: #ffffff;
padding: 30px;
border: 1px solid #e5e7eb;
border-top: none;
}
.button {
display: inline-block;
background: #0066CC;
color: white !important;
padding: 12px 24px;
text-decoration: none;
border-radius: 6px;
margin: 20px 0;
font-weight: 600;
}
.button:hover {
background: #0052A3;
}
.footer {
background: #f9fafb;
padding: 20px;
text-align: center;
font-size: 12px;
color: #6b7280;
border-radius: 0 0 8px 8px;
}
.divider {
border-top: 1px solid #e5e7eb;
margin: 20px 0;
}
</style>
</head>
<body>
<div class="header">
<h1 style="margin: 0;">QA Team Portal</h1>
<p style="margin: 10px 0 0 0; opacity: 0.9;">Evoke Technologies</p>
</div>
<div class="content">
{% block content %}{% endblock %}
</div>
<div class="footer">
<p>This email was sent by QA Team Portal</p>
<p>If you have questions, contact us at {{ support_email }}</p>
<div class="divider"></div>
<p>© 2025 Evoke Technologies. All rights reserved.</p>
</div>
</body>
</html>
Location: backend/app/templates/email/password_reset.html
{% extends "base.html" %}
{% block content %}
<h2 style="color: #0066CC; margin-top: 0;">Password Reset Request</h2>
<p>Hello,</p>
<p>We received a request to reset your password for your QA Team Portal account.</p>
<p>Click the button below to reset your password:</p>
<center>
<a href="{{ reset_url }}" class="button">Reset Password</a>
</center>
<p><strong>This link will expire in {{ expiry_minutes }} minutes.</strong></p>
<div class="divider"></div>
<p style="font-size: 14px; color: #6b7280;">
If you didn't request a password reset, you can safely ignore this email. Your password will not be changed.
</p>
<p style="font-size: 14px; color: #6b7280;">
If the button doesn't work, copy and paste this link into your browser:<br>
<a href="{{ reset_url }}" style="color: #0066CC; word-break: break-all;">{{ reset_url }}</a>
</p>
{% endblock %}
Location: backend/app/templates/email/welcome.html
{% extends "base.html" %}
{% block content %}
<h2 style="color: #0066CC; margin-top: 0;">Welcome to QA Team Portal!</h2>
<p>Hi {{ name }},</p>
<p>Welcome to the QA Team Portal! Your account has been successfully created.</p>
<p>You can now access the admin portal to manage team members, tools, resources, and more.</p>
<center>
<a href="{{ login_url }}" class="button">Go to Admin Portal</a>
</center>
<h3 style="color: #333; font-size: 18px;">What you can do:</h3>
<ul>
<li>Manage team member profiles</li>
<li>Post latest updates and announcements</li>
<li>Organize QA tools by category</li>
<li>Share resources and documentation</li>
<li>Publish research and findings</li>
</ul>
<div class="divider"></div>
<p>If you have any questions or need assistance, don't hesitate to reach out to our support team.</p>
<p>Best regards,<br>QA Team Portal Team</p>
{% endblock %}
Location: backend/app/templates/email/account_locked.html
{% extends "base.html" %}
{% block content %}
<h2 style="color: #EF4444; margin-top: 0;">Account Locked</h2>
<p>Hi {{ name }},</p>
<p>Your QA Team Portal account has been temporarily locked due to multiple failed login attempts.</p>
<p><strong>Your account will be automatically unlocked in {{ unlock_time }}.</strong></p>
<div class="divider"></div>
<h3 style="color: #333; font-size: 18px;">What happened?</h3>
<p>For your security, we lock accounts after 5 failed login attempts. This helps protect your account from unauthorized access.</p>
<h3 style="color: #333; font-size: 18px;">What should I do?</h3>
<ul>
<li>Wait for the lockout period to expire ({{ unlock_time }})</li>
<li>If you forgot your password, use the "Forgot Password" link on the login page</li>
<li>If you didn't attempt to login, contact our support team immediately</li>
</ul>
<div class="divider"></div>
<p style="font-size: 14px; color: #6b7280;">
If you believe this was a mistake or need immediate access, please contact support at {{ support_email }}.
</p>
{% endblock %}
Location: backend/app/api/v1/endpoints/auth.py
from fastapi import APIRouter, Depends, BackgroundTasks, HTTPException
from sqlalchemy.orm import Session
from app.services.email_service import EmailService
from app.api.deps import get_db
from app.crud.user import user as user_crud
import secrets
router = APIRouter()
@router.post("/forgot-password")
async def forgot_password(
email: str,
background_tasks: BackgroundTasks,
db: Session = Depends(get_db)
):
"""Send password reset email."""
user = await user_crud.get_by_email(db, email=email)
# Don't reveal if user exists
if not user:
return {"message": "If the email exists, a reset link has been sent"}
# Generate reset token
reset_token = secrets.token_urlsafe(32)
expires = datetime.utcnow() + timedelta(minutes=15)
# Store token in database
await user_crud.set_reset_token(
db,
user_id=user.id,
token=reset_token,
expires=expires
)
# Send email in background
await EmailService.send_password_reset_email(
email=user.email,
reset_token=reset_token,
background_tasks=background_tasks
)
return {"message": "If the email exists, a reset link has been sent"}
@router.post("/test-email")
async def test_email(
recipient: str,
current_user: User = Depends(get_current_admin)
):
"""Test email configuration (admin only)."""
success = await EmailService.send_test_email(recipient)
if success:
return {"message": f"Test email sent to {recipient}"}
else:
raise HTTPException(500, "Failed to send test email")
Using Celery for Email Queue:
# backend/app/tasks/email_tasks.py
from celery import Celery
from app.services.email_service import EmailService
celery_app = Celery('tasks', broker='redis://localhost:6379/0')
@celery_app.task
def send_email_task(recipients, subject, body, html=None):
"""Background task to send email."""
import asyncio
loop = asyncio.get_event_loop()
return loop.run_until_complete(
EmailService.send_email(recipients, subject, body, html)
)
@celery_app.task
def send_password_reset_task(email, reset_token):
"""Background task to send password reset email."""
import asyncio
loop = asyncio.get_event_loop()
return loop.run_until_complete(
EmailService.send_password_reset_email(email, reset_token)
)
# tests/test_email_service.py
import pytest
from app.services.email_service import EmailService
@pytest.mark.asyncio
async def test_send_test_email():
"""Test sending a simple email."""
success = await EmailService.send_test_email("[email protected]")
assert success is True
@pytest.mark.asyncio
async def test_send_password_reset_email():
"""Test password reset email."""
success = await EmailService.send_password_reset_email(
email="[email protected]",
reset_token="test-token-123"
)
assert success is True
@pytest.mark.asyncio
async def test_send_welcome_email():
"""Test welcome email."""
success = await EmailService.send_welcome_email(
email="[email protected]",
name="New User"
)
assert success is True
MAIL_SERVER=smtp.gmail.com
MAIL_PORT=587
[email protected]
MAIL_PASSWORD=your-app-password
MAIL_SERVER=smtp.sendgrid.net
MAIL_PORT=587
MAIL_USERNAME=apikey
MAIL_PASSWORD=your-sendgrid-api-key
MAIL_SERVER=email-smtp.us-east-1.amazonaws.com
MAIL_PORT=587
MAIL_USERNAME=your-smtp-username
MAIL_PASSWORD=your-smtp-password
Email not sending:
Template not found:
Authentication failed:
✅ Email service configured (FastAPI-Mail) ✅ SMTP connection established ✅ Password reset email template created ✅ Welcome email template created ✅ Account locked email template created ✅ Background tasks integrated ✅ Test endpoint created ✅ Error logging implemented ✅ Templates use responsive HTML ✅ All emails tested successfully
xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience