Skip to content

SMTP Backend

The SMTP backend (SMTPBackend) sends emails via any SMTP server — Gmail, Mailgun, your own Postfix, etc.

Installation

pip install amsdal_mail[smtp]

The [smtp] extra installs aiosmtplib for async support.

Configuration

Environment Variables

Variable Default Description
AMSDAL_EMAIL_HOST localhost SMTP server hostname
AMSDAL_EMAIL_PORT 25 SMTP server port
AMSDAL_EMAIL_USER Authentication username
AMSDAL_EMAIL_PASSWORD Authentication password
AMSDAL_EMAIL_USE_TLS false Use STARTTLS (upgrade to TLS after connecting)
AMSDAL_EMAIL_USE_SSL false Use SSL/TLS from the start
AMSDAL_EMAIL_TIMEOUT 30 Connection timeout in seconds

Constructor Parameters

All constructor parameters mirror the env vars above and take precedence over them:

from amsdal_mail import get_connection

conn = get_connection(
    'smtp',
    host='smtp.gmail.com',
    port=587,
    username='you@gmail.com',
    password='your-app-password',
    use_tls=True,
)

Gmail Setup

Gmail requires an App Password (not your regular password). Enable 2-Step Verification first, then generate an App Password.

export AMSDAL_EMAIL_BACKEND=smtp
export AMSDAL_EMAIL_HOST=smtp.gmail.com
export AMSDAL_EMAIL_PORT=587
export AMSDAL_EMAIL_USER=you@gmail.com
export AMSDAL_EMAIL_PASSWORD=your-app-password
export AMSDAL_EMAIL_USE_TLS=true
from amsdal_mail import send_mail

status = send_mail(
    subject='Hello from AMSDAL',
    message='This email was sent via Gmail SMTP.',
    from_email='you@gmail.com',
    recipient_list=['recipient@example.com'],
)

TLS vs SSL

Mode Port How It Works
STARTTLS (use_tls=True) 587 Connects in plain text, then upgrades to TLS
SSL/TLS (use_ssl=True) 465 Encrypted from the start
Neither 25 No encryption (not recommended)

Warning

Do not set both use_tls and use_ssl to True — they are mutually exclusive.

Connection Pooling

Use the context manager to send multiple messages over a single SMTP connection:

from amsdal_mail import get_connection, EmailMessage

with get_connection('smtp') as conn:
    for user in users:
        msg = EmailMessage(
            subject='Notification',
            body=f'Hello {user.name}!',
            from_email='noreply@example.com',
            to=[user.email],
        )
        conn.send_messages([msg])

The connection is opened on __enter__ and closed on __exit__.

HTML Email with Attachments

from amsdal_mail import EmailMessage, Attachment, get_connection

msg = EmailMessage(
    subject='Invoice #1234',
    body='Please find your invoice attached.',
    html_body='<h1>Invoice #1234</h1><p>See attachment.</p>',
    from_email='billing@example.com',
    to=['customer@example.com'],
    attachments=[
        Attachment(
            filename='invoice-1234.pdf',
            content=pdf_bytes,
            mimetype='application/pdf',
        ),
    ],
)

conn = get_connection('smtp')
status = conn.send_messages([msg])

Inline Images

from amsdal_mail import EmailMessage, Attachment, get_connection

msg = EmailMessage(
    subject='Newsletter',
    body='See HTML version.',
    html_body='<img src="cid:header"><h1>Weekly Update</h1>',
    from_email='news@example.com',
    to=['subscriber@example.com'],
    attachments=[
        Attachment(
            filename='header.png',
            content=header_bytes,
            mimetype='image/png',
            content_id='header',
        ),
    ],
)

conn = get_connection('smtp')
status = conn.send_messages([msg])

Async

The async version uses aiosmtplib:

from amsdal_mail import asend_mail

status = await asend_mail(
    subject='Hello',
    message='Sent asynchronously via SMTP.',
    from_email='noreply@example.com',
    recipient_list=['user@example.com'],
)

Or with EmailMessage directly:

from amsdal_mail import EmailMessage, get_connection

conn = get_connection('smtp')
status = await conn.asend_messages([msg])

Per-Recipient Status

SMTP returns per-recipient accept/reject results. The backend maps these to SendStatus:

  • Accepted recipients get status 'sent'
  • Rejected with SMTP code >= 500 get 'rejected'
  • Rejected with SMTP code < 500 get 'failed'

Note

SMTP does not return message IDs, so RecipientStatus.message_id is always None for the SMTP backend.