Skip to content

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