AMSDAL Mail¶
amsdal_mail is a universal email integration plugin for the AMSDAL Framework. It provides a single API for sending emails through different providers — SMTP, Amazon SES, or a console/dummy backend for development and testing.
Installation¶
# Core (console + dummy backends only)
pip install amsdal_mail
# With SMTP support (adds aiosmtplib for async)
pip install amsdal_mail[smtp]
# With Amazon SES support (adds boto3 + aioboto3)
pip install amsdal_mail[ses]
# All backends
pip install amsdal_mail[all]
Plugin Registration¶
Add to your .env file:
AMSDAL_CONTRIBS='amsdal_mail.app.MailAppConfig'
Quick Start¶
send_mail¶
The simplest way to send an email:
from amsdal_mail import send_mail
status = send_mail(
subject='Welcome!',
message='Thanks for signing up.',
from_email='noreply@example.com',
recipient_list=['user@example.com'],
)
print(status.is_success) # True
Async¶
from amsdal_mail import asend_mail
status = await asend_mail(
subject='Welcome!',
message='Thanks for signing up.',
from_email='noreply@example.com',
recipient_list=['user@example.com'],
)
Both functions accept additional keyword arguments that are passed to EmailMessage: cc, bcc, reply_to, html_message, tags, metadata, attachments, etc.
EmailMessage¶
For full control, create an EmailMessage directly:
from amsdal_mail import EmailMessage, get_connection
msg = EmailMessage(
subject='Monthly Report',
body='Please find the report attached.',
html_body='<h1>Monthly Report</h1><p>See attachment.</p>',
from_email='reports@example.com',
to=['manager@example.com'],
cc=['team-lead@example.com'],
bcc=['archive@example.com'],
reply_to=['support@example.com'],
headers={'X-Priority': '1'},
tags=['reports', 'monthly'],
metadata={'department': 'finance', 'month': '2026-03'},
)
conn = get_connection('smtp')
status = conn.send_messages([msg])
EmailMessage Fields¶
| Field | Type | Default | Description |
|---|---|---|---|
subject |
str |
required | Subject line |
body |
str |
'' |
Plain text body (required unless template_id is set) |
from_email |
str |
required | Sender address |
to |
list[str] |
required | Primary recipients |
cc |
list[str] |
[] |
Carbon copy recipients |
bcc |
list[str] |
[] |
Blind carbon copy recipients |
reply_to |
list[str] |
[] |
Reply-to addresses |
html_body |
str \| None |
None |
HTML version of body |
attachments |
list[Attachment] |
[] |
File attachments |
headers |
dict[str, str] |
{} |
Custom email headers |
tags |
list[str] |
[] |
Tags for categorization and filtering |
metadata |
dict[str, str] |
{} |
Key-value metadata for tracking |
template_id |
str \| None |
None |
ESP template ID (SES) |
merge_data |
dict[str, dict] \| None |
None |
Per-recipient template variables |
merge_global_data |
dict \| None |
None |
Global template variables |
track_opens |
bool |
False |
Enable open tracking (ESP-dependent) |
track_clicks |
bool |
False |
Enable click tracking (ESP-dependent) |
Note
to, cc, bcc, and reply_to accept a single string — it's automatically converted to a list.
Attachments¶
Regular Attachments¶
from amsdal_mail import Attachment, EmailMessage
attachment = Attachment(
filename='report.pdf',
content=pdf_bytes,
mimetype='application/pdf',
)
msg = EmailMessage(
subject='Report',
body='See attached.',
from_email='noreply@example.com',
to=['user@example.com'],
attachments=[attachment],
)
Inline Images (CID)¶
Embed images directly in HTML using content_id:
logo = Attachment(
filename='logo.png',
content=logo_bytes,
mimetype='image/png',
content_id='logo',
)
msg = EmailMessage(
subject='Newsletter',
body='See HTML version.',
html_body='<img src="cid:logo"><p>Welcome!</p>',
from_email='news@example.com',
to=['subscriber@example.com'],
attachments=[logo],
)
SendStatus¶
Every send operation returns a SendStatus:
status = send_mail(...)
# Check overall result
status.is_success # True if all recipients are 'sent' or 'queued'
status.has_failures # True if any recipient is 'failed', 'rejected', or 'invalid'
status.message_id # ESP message ID (str, set[str], or None)
# Per-recipient details
status.recipients # dict[str, RecipientStatus]
status.get_successful_recipients() # list of email addresses
status.get_failed_recipients() # list of email addresses
# Raw ESP response (for debugging)
status.esp_response
SendStatusType¶
Per-recipient statuses:
| Status | Description |
|---|---|
'sent' |
ESP accepted and will deliver |
'queued' |
ESP will retry later |
'invalid' |
Bad email address |
'rejected' |
Blacklisted or rejected by ESP |
'failed' |
Other send failure |
'unknown' |
Status could not be determined |
Backends¶
| Backend | Name | Package | Use Case |
|---|---|---|---|
| Console | 'console' |
built-in | Development — prints emails to stdout |
| Dummy | 'dummy' |
built-in | Testing — doesn't send, optionally stores messages |
| SMTP | 'smtp' |
amsdal_mail[smtp] |
Any SMTP server (Gmail, Mailgun, etc.) |
| Amazon SES | 'ses' |
amsdal_mail[ses] |
AWS Simple Email Service |
Selecting a Backend¶
Via environment variable:
export AMSDAL_EMAIL_BACKEND=smtp
Via code:
from amsdal_mail import get_connection
conn = get_connection('ses')
status = conn.send_messages([msg])
Custom backend (full dotted path):
conn = get_connection('myapp.backends.CustomBackend')
Connection Pooling¶
Use the context manager to reuse connections across multiple sends:
from amsdal_mail import get_connection, EmailMessage
with get_connection('smtp') as conn:
for recipient in recipients:
msg = EmailMessage(
subject='Update',
body='...',
from_email='noreply@example.com',
to=[recipient],
)
conn.send_messages([msg])
fail_silently¶
When fail_silently=True, exceptions are suppressed and failed recipients are marked in SendStatus instead of raising:
from amsdal_mail import send_mail
status = send_mail(
subject='Test',
message='...',
from_email='noreply@example.com',
recipient_list=['user@example.com'],
fail_silently=True,
)
if status.has_failures:
print(f'Failed: {status.get_failed_recipients()}')
Exceptions¶
| Exception | Description |
|---|---|
EmailError |
Base exception for all mail errors |
ConfigurationError |
Bad backend configuration |
EmailConnectionError |
Failed to connect to ESP |
SendError |
Failed to send message |
All are importable from amsdal_mail:
from amsdal_mail import EmailError, ConfigurationError, EmailConnectionError, SendError