$ show sabuhish/fastapi-mail
fastapi-mail is a lightweight async Python library for sending emails from FastAPI/Starlette applications. It provides a clean, Pydantic-driven configuration model (ConnectionConfig), a message builder (MessageSchema) supporting HTML, plain text, Jinja2 templates, file attachments, CC/BCC/reply-to, and custom SMTP headers. The core FastMail class handles async SMTP delivery via aiosmtplib, with optional Redis-backed email validation utilities (DefaultChecker) and a WhoIsXmlApi wrapper. The architecture is simple and single-purpose: a library to be imported into FastAPI projects. It has comprehensive test coverage (22+ async tests) and modern async patterns throughout. The codebase is well-maintained with CI/CD, linting, and documentation. While the library does not exhibit groundbreaking innovation, it is a solid, production-ready solution for email sending in async Python web applications.
The LLM identified 2 files worth highlighting for reuse:
DefaultChecker is an async email validation utility that fetches temporary email domains from a public gist, stores them in memory or optionally in Redis, and provides methods for disposable email detection, domain/email blacklisting, MX record checking, and more. It demonstrates a clean pattern for integrating an optional Redis backend (via duck-typing with a redis_client property) and async I/O (httpx for fetching domains, dns.asyncresolver for MX lookups). The class can be extracted and reused in any project needing email validation with or without Redis, though it requires refactoring to decouple from the library's exception classes.
MailMsg is a MIME message builder that constructs email.message.Message objects from a MessageSchema. It handles multipart messages, file attachments (with optional custom MIME type and headers), alternative bodies, charset encoding, and RFC 2047 encoding for non-ASCII display names. The pattern of separating message construction from transport is clean and reusable. However, it is tightly coupled to the MessageSchema Pydantic model, so extraction would require also extracting a subset of the schema or defining a compatible interface.