主页
文章
分类
标签
关于
FastAPI 邮箱发送功能
发布于: 2025-12-6   更新于: 2025-12-6   收录于: Fastapi
文章字数: 1372   阅读时间: 3 分钟   阅读量:

前置介绍

本实验参考知了传课的最新教程实现,利用fastapi-mail发送邮箱验证码,实验采用qq邮箱来进行邮箱的发送和接受

安装插件

1
pip install fastapi-mail

邮箱配置

首先打开QQ邮箱的首页 点击左上角 设置

示例

然后点开左边的账号与安全

示例

再进入安全设置

示例

我们绑定完手机之后,去生成授权码 示例

之后我们就拿到自己的授权码了 示例

注意:授权码用于 SMTP 身份验证,不是邮箱登录密码,每次生成的都不一样,如果被别人弄走了,重新生成一个即可。

Fastapi-Mail配置

在自己的配置文件(我这里是settings),放置自己的配置,推荐使用pydantic_setting的BaseSettings:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# settings.py
from pydantic_settings import BaseSettings

class MailSettings(BaseSettings):
    MAIL_USERNAME: str
    MAIL_PASSWORD: str
    MAIL_FROM: str
    MAIL_PORT: int = 587
    MAIL_SERVER: str = "smtp.qq.com"
    MAIL_FROM_NAME: str = "FastAPI Mail Service"
    MAIL_STARTTLS: bool = True
    MAIL_SSL_TLS: bool = False
    USE_CREDENTIALS: bool = True
    VALIDATE_CERTS: bool = True

    class Config:
        env_file = ".env"

同时创建.env文件:

1
2
3
MAIL_USERNAME=your_email@qq.com
MAIL_PASSWORD=your_16_digit_auth_code
MAIL_FROM=your_email@qq.com

创建 FastMail 实例与依赖注入

创建工厂函数

先写一个FastMail实例的工厂函数,每次调用都返回一个新的实例,防止阻塞

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
from fastapi_mail import FastMail, ConnectionConfig
from pydantic import SecretStr, EmailStr
from settings import base_settings

def create_mail_instance() -> FastMail:

    mail_config = ConnectionConfig(
        MAIL_USERNAME=base_settings.MAIL_USERNAME,
        MAIL_PASSWORD=base_settings.MAIL_PASSWORD,
        MAIL_FROM=base_settings.MAIL_FROM,
        MAIL_PORT=base_settings.MAIL_PORT,
        MAIL_SERVER=base_settings.MAIL_SERVER,
        MAIL_FROM_NAME=base_settings.MAIL_FROM_NAME,
        MAIL_STARTTLS=base_settings.MAIL_STARTTLS,
        MAIL_SSL_TLS=base_settings.MAIL_SSL_TLS,
        USE_CREDENTIALS=True,
        VALIDATE_CERTS=True,
    )

    return FastMail(mail_config)

创建依赖函数

根据工厂函数创建依赖

1
2
3
4
5
6
7
from fastapi_mail import FastMail

from models import AsyncSessionFactory
from core.mail import create_mail_instance

async def get_mail() -> FastMail:
    return create_mail_instance()

mail发送测试接口

写一个api测试一下邮件发送:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from fastapi import FastAPI, Depends
from fastapi_mail import FastMail, MessageSchema, MessageType
from aiosmtplib import SMTPResponseException

from dependencies import get_mail

app = FastAPI()

@app.get("/")
async def root():
    return {"msg": "Hello World!"}

@app.get("/mail/test")
async def test_mail(email: str, mail: FastMail = Depends(get_mail)):
    message = MessageSchema(
        subject="测试邮件",
        recipients=[email],
        body=f"HELLO {email}",
        subtype=MessageType.plain
    )

    await mail.send_message(message)

    return {"msg": "发送成功"}

这里引入一个MessageSchema的pydatic模型用于数据校验,具体就是这四个东西:

  • subject=“测试邮件”,
  • recipients=[email],
  • body=f"HELLO {email}",
  • subtype=MessageType.plain

注意 QQ 邮箱 SMTP 限制:

  • 使用端口 587(STARTTLS)或 465(SSL)。本文配置使用 587。
  • 每日发送限额约为 500 封,超出后会被临时限制。

fastapi-mail 已知问题

在部分 SMTP 服务器(如 QQ 邮箱)关闭连接时,会返回非标准字节流,导致底层 aiosmtplib 抛出 code=-1 的异常。此为协议兼容性问题,邮件已成功投递,可安全忽略该特定异常。 alt text

优化错误处理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@app.get("/mail/test")
async def test_mail(email: str, mail: FastMail = Depends(get_mail)):
    message = MessageSchema(
        subject="测试邮件",
        recipients=[email],
        body=f"HELLO {email}",
        subtype=MessageType.plain
    )
    try:
        await mail.send_message(message)
    except SMTPResponseException as e:
        if e.code == -1 and b"\\x00\\x00\\x00" in str(e).encode():
            print("忽略QQ邮箱SMTP关闭阶段的非标准式响应,邮箱已经发送成功")
    
    return {"msg": "发送成功"}

在获取到对应错误的时候,忽略一下即可

生产环境建议

  • 敏感配置必须通过环境变量管理,禁止硬编码。
  • 对验证码接口实施频率限制(如每 IP 每分钟 1 次)。
  • 使用 HTML 邮件提升用户体验:
    1
    2
    3
    4
    5
    6
    
    message = MessageSchema(
        subject="验证码",
        recipients=[email],
        body="<h2>您的验证码是:<strong>123456</strong></h2>",
        subtype=MessageType.html
    )
    
  • 记录邮件发送日志,便于问题追踪。

HTML 验证码邮件示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
@app.get("/mail/verify")
async def send_verify_code(
    email: str = Query(..., regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"),
    code: str = Query(..., min_length=6, max_length=6),
    mail: FastMail = Depends(get_mail)
):
    message = MessageSchema(
        subject="【验证码】您的登录验证码",
        recipients=[email],
        body=f"""
        <div>
            <h3>验证码:{code}</h3>
            <p>5 分钟内有效,请勿泄露。</p>
        </div>
        """,
        subtype=MessageType.html
    )
    await mail.send_message(message)
    return {"message": "验证码已发送"}