aiosmtplib

Quickstart

import asyncio
from email.message import EmailMessage

import aiosmtplib

message = EmailMessage()
message["From"] = "root@localhost"
message["To"] = "somebody@example.com"
message["Subject"] = "Hello World!"
message.set_content("Sent via aiosmtplib")

loop = asyncio.get_event_loop()
loop.run_until_complete(aiosmtplib.send(message, hostname="127.0.0.1", port=25))

Requirements

Python 3.5.2+, compiled with SSL support, is required.

Bug reporting

Bug reports (and feature requests) are welcome via Github issues.

User’s Guide

Sending Messages

Sending Message Objects

To send a message, create an email.message.EmailMessage object, set appropriate headers (“From” and one of “To”, “Cc” or “Bcc”, at minimum), then pass it to send() with the hostname and port of an SMTP server.

For details on creating email.message.EmailMessage objects, see the stdlib documentation examples.

Note

Confusingly, email.message.Message objects are part of the legacy email API (prior to Python 3.3), while email.message.EmailMessage objects support email policies other than the older email.policy.Compat32.

Use email.message.EmailMessage where possible; it makes headers easier to work with.

import asyncio
from email.message import EmailMessage

import aiosmtplib

async def send_hello_world():
    message = EmailMessage()
    message["From"] = "root@localhost"
    message["To"] = "somebody@example.com"
    message["Subject"] = "Hello World!"
    message.set_content("Sent via aiosmtplib")

    await aiosmtplib.send(message, hostname="127.0.0.1", port=1025)

event_loop = asyncio.get_event_loop()
event_loop.run_until_complete(send_hello_world())

Multipart Messages

Pass email.mime.multipart.MIMEMultipart objects to send() to send messages with both HTML text and plain text alternatives.

from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText


message = MIMEMultipart("alternative")
message["From"] = "root@localhost"
message["To"] = "somebody@example.com"
message["Subject"] = "Hello World!"

plain_text_message = MIMEText("Sent via aiosmtplib", "plain", "utf-8")
html_message = MIMEText(
    "<html><body><h1>Sent via aiosmtplib</h1></body></html>", "html", "utf-8"
)
message.attach(plain_text_message)
message.attach(html_message)

Sending Raw Messages

You can also send a str or bytes message, by providing the sender and recipients keyword arguments.

import asyncio

import aiosmtplib

async def send_hello_world():
    message = """To: somebody@example.com
    From: root@localhost
    Subject: Hello World!

    Sent via aiosmtplib
    """

    await aiosmtplib.send(
        message,
        sender="root@localhost",
        recipients=["somebody@example.com"],
        hostname="127.0.0.1",
        port=1025
    )

event_loop = asyncio.get_event_loop()
event_loop.run_until_complete(send_hello_world())

Authentication

To authenticate, pass the username and password keyword arguments to send().

await send(
    message,
    hostname="127.0.0.1",
    port=1025,
    username="test",
    password="test"
)

Connection Options

Connecting Over TLS/SSL

If an SMTP server supports direct connection via TLS/SSL, pass use_tls=True.

await send(message, hostname="smtp.gmail.com", port=465, use_tls=True)

STARTTLS connections

Many SMTP servers support the STARTTLS extension over port 587. When using STARTTLS, the initial connection is made over plaintext, and after connecting a STARTTLS command is sent, which initiates the upgrade to a secure connection. To connect to a server that uses STARTTLS, set start_tls to True.

await send(message, hostname="smtp.gmail.com", port=587, start_tls=True)

The SMTP Client Class

Use the SMTP class as a client directly when you want more control over the email sending process than the send() async function provides.

Connecting to an SMTP Server

Initialize a new SMTP instance, then await its SMTP.connect() coroutine. Initializing an instance does not automatically connect to the server, as that is a blocking operation.

import asyncio

from aiosmtplib import SMTP


client = SMTP()
event_loop =  asyncio.get_event_loop()
event_loop.run_until_complete(client.connect(hostname="127.0.0.1", port=1025))

Connecting over TLS/SSL

If an SMTP server supports direct connection via TLS/SSL, pass use_tls=True when initializing the SMTP instance (or when calling SMTP.connect()).

smtp_client = aiosmtplib.SMTP(hostname="smtp.gmail.com", port=465, use_tls=True)
await smtp_client.connect()

STARTTLS connections

Many SMTP servers support the STARTTLS extension over port 587. When using STARTTLS, the initial connection is made over plaintext, and after connecting a STARTTLS command is sent which initiates the upgrade to a secure connection. To connect to a server that uses STARTTLS, set use_tls to False when connecting, and call SMTP.starttls() on the client.

smtp_client = aiosmtplib.SMTP(hostname="smtp.gmail.com", port=587, use_tls=False)
await smtp_client.connect()
await smtp_client.starttls()

Connecting via async context manager

Instances of the SMTP class can also be used as an async context manager, which will automatically connect/disconnect on entry/exit.

import asyncio
from email.message import EmailMessage

from aiosmtplib import SMTP


async def say_hello():
    message = EmailMessage()
    message["From"] = "root@localhost"
    message["To"] = "somebody@example.com"
    message["Subject"] = "Hello World!"
    message.set_content("Sent via aiosmtplib")

    smtp_client = SMTP(hostname="127.0.0.1", port=1025)
    async with smtp_client:
        await smtp_client.send_message(message)

event_loop = asyncio.get_event_loop()
event_loop.run_until_complete(say_hello())

Sending Messages

SMTP.send_message()

Use this method to send email.message.EmailMessage objects, including email.mime subclasses such as email.mime.text.MIMEText.

For details on creating email.message.Message objects, see the stdlib documentation examples.

import asyncio
from email.mime.text import MIMEText

from aiosmtplib import SMTP


mime_message = MIMEText("Sent via aiosmtplib")
mime_message["From"] = "root@localhost"
mime_message["To"] = "somebody@example.com"
mime_message["Subject"] = "Hello World!"

async def send_with_send_message(message):
    smtp_client = SMTP(hostname="127.0.0.1", port=1025)
    await smtp_client.connect()
    await smtp_client.send_message(message)
    await smtp_client.quit()

event_loop = asyncio.get_event_loop()
event_loop.run_until_complete(send_with_send_message(mime_message))

Pass email.mime.multipart.MIMEMultipart objects to SMTP.send_message() to send messages with both HTML text and plain text alternatives.

from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

message = MIMEMultipart("alternative")
message["From"] = "root@localhost"
message["To"] = "somebody@example.com"
message["Subject"] = "Hello World!"

message.attach(MIMEText("hello", "plain", "utf-8"))
message.attach(MIMEText("<html><body><h1>Hello</h1></body></html>", "html", "utf-8"))

smtp_client = SMTP(hostname="127.0.0.1", port=1025)
event_loop.run_until_complete(smtp_client.connect())
event_loop.run_until_complete(smtp_client.send_message(message))
event_loop.run_until_complete(smtp_client.quit())

SMTP.sendmail()

Use SMTP.sendmail() to send raw messages. Note that when using this method, you must format the message headers yourself.

import asyncio

from aiosmtplib import SMTP


sender = "root@localhost"
recipients = ["somebody@example.com"]
message = """To: somebody@example.com
From: root@localhost
Subject: Hello World!

Sent via aiosmtplib
"""

async def send_with_sendmail():
    smtp_client = SMTP(hostname="127.0.0.1", port=1025)
    await smtp_client.connect()
    await smtp_client.sendmail(sender, recipients, message)
    await smtp_client.quit()

event_loop = asyncio.get_event_loop()
event_loop.run_until_complete(send_with_sendmail())

Timeouts

All commands accept a timeout keyword argument of a numerical value in seconds. This value is used for all socket operations, and will raise SMTPTimeoutError if exceeded. Timeout values passed to send(), SMTP.__init__() or SMTP.connect() will be used as the default value for commands executed on the connection.

The default timeout is 60 seconds.

Parallel Execution

SMTP is a sequential protocol. Multiple commands must be sent to send an email, and they must be sent in the correct sequence. As a consequence of this, executing multiple SMTP.send_message() tasks in parallel (i.e. with asyncio.gather()) is not any more efficient than executing in sequence, as the client must wait until one mail is sent before beginning the next.

If you have a lot of emails to send, consider creating multiple connections (SMTP instances) and splitting the work between them.

API Reference

The send Coroutine

The SMTP Class

Server Responses

Status Codes

Exceptions

Changelog

1.1.3

  • Feature: add pause and resume writing methods to SMTPProcotol, via asyncio.streams.FlowControlMixin (thanks ikrivosheev).

  • Bugfix: allow an empty sender (credit ikrivosheev)

  • Cleanup: more useful error message when login called without TLS

1.1.2

  • Bugfix: removed docs and tests from wheel, they should only be in the source distribution.

1.1.1

  • Bugfix: Fix handling of sending legacy email API (Message) objects.

  • Bugfix: Fix SMTPNotSupported error with UTF8 sender/recipient names on servers that don’t support SMTPUTF8.

1.1.0

  • Feature: Added send coroutine api.

  • Feature: Added SMTPUTF8 support for UTF8 chars in addresses.

  • Feature: Added connected socket and Unix socket path connection options.

  • Feature: Wait until the connect coroutine is awaited to get the event loop. Passing an explicit event loop via the loop keyword argument is deprecated and will be removed in version 2.0.

  • Cleanup: Set context for timeout and connection exceptions properly.

  • Cleanup: Use built in start_tls method on Python 3.7+.

  • Cleanup: Timeout correctly if TLS handshake takes too long on Python 3.7+.

  • Cleanup: Updated SMTPProcotol class and removed StreamReader/StreamWriter usage to remove deprecation warnings in 3.8.

  • Bugfix: EHLO/HELO if required before any command, not just when using higher level commands.

  • Cleanup: Replaced asserts in functions with more useful errors (e.g. RuntimeError).

  • Cleanup: More useful error messages for timeouts (thanks ikrivosheev!), including two new exception classes, SMTPConnectTimeoutError and SMTPReadTimeoutError

1.0.6

  • Bugfix: Set default timeout to 60 seconds as per documentation (previously it was unlimited).

1.0.5

  • Bugfix: Connection is now closed if an error response is received immediately after connecting.

1.0.4

  • Bugfix: Badly encoded server response messages are now decoded to utf-8, with error chars escaped.

  • Cleanup: Removed handling for exceptions not raised by asyncio (in SMTPProtocol._readline)

1.0.3

  • Bugfix: Removed buggy close connection on __del__

  • Bugfix: Fixed old style auth method parsing in ESMTP response.

  • Bugfix: Cleanup transport on exception in connect method.

  • Cleanup: Simplified SMTPProtocol.connection_made, __main__

1.0.2

  • Bugfix: Close connection lock on on SMTPServerDisconnected

  • Feature: Added cert_bundle argument to connection init, connect and starttls methods

  • Bugfix: Disconnected clients would raise SMTPResponseException: (-1 …) instead of SMTPServerDisconnected

1.0.1

  • Bugfix: Commands were getting out of order when using the client as a context manager within a task

  • Bugfix: multiple tasks calling connect would get confused

  • Bugfix: EHLO/HELO responses were being saved even after disconnect

  • Bugfix: RuntimeError on client cleanup if event loop was closed

  • Bugfix: CRAM-MD5 auth was not working

  • Bugfix: AttributeError on STARTTLS under uvloop

1.0.0

Initial feature complete release with stable API; future changes will be documented here.

Indices and tables