Using the concrete I/O implementations
In addition to the sans-io protocol implementation, this library also provides both an
asynchronous and a synchronous SMTP client class (AsyncSMTPClient
and
SyncSMTPClient
, respectively).
Most SMTP servers, however, require some form of authentication. While it would be unfeasible to provide solutions for every possible situation, the examples below should cover some very common cases and should give you a general idea of how to work with SMTP authentication.
For the OAuth2 examples (further below), you need to install a couple dependencies:
Sending mail via a local SMTP server
from email.message import EmailMessage
import anyio
from smtpproto.auth import PlainAuthenticator
from smtpproto.client import AsyncSMTPClient
async def main():
async with AsyncSMTPClient(host='localhost', port=25) as client:
await client.send_message(message)
# If your SMTP server requires basic authentication, this is where you enter that
# info
authenticator = PlainAuthenticator(username='myuser', password='mypassword')
# The message you want to send
message = EmailMessage()
message['From'] = 'my.name@mydomain.com'
message['To'] = 'somebody@somewhere'
message['Subject'] = 'Test from smtpproto'
message.set_content('This is a test.')
# Actually sends the message by running main()
anyio.run(main)
Sending mail via Gmail
The developer documentation for the G Suite describes how to use the XOAUTH2
mechanism for authenticating against the Gmail SMTP server. The following is a practical
example of how to extend the OAuth2Authenticator
class to obtain an
access token and use it to send an email via Gmail.
The following example assumes the presence of an existing G Suite service account
authorized to send email via SMTP (using the https://mail.google.com/
scope).
from datetime import datetime, timedelta
from email.message import EmailMessage
import aiohttp
import jwt
import anyio
from smtpproto.auth import OAuth2Authenticator
from smtpproto.client import AsyncSMTPClient
class GMailAuthenticator(OAuth2Authenticator):
def __init__(self, username: str, client_id: str, private_key: str):
super().__init__(username)
self.client_id = client_id
self.private_key = private_key
async def get_token_async(self):
webtoken = jwt.encode({
'iss': self.client_id,
'scope': 'https://mail.google.com/',
'aud': 'https://oauth2.googleapis.com/token',
'exp': datetime.utcnow() + timedelta(minutes=1),
'iat': datetime.utcnow(),
'sub': self.username
}, self.private_key, algorithm='RS256')
data = {
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
'assertion': webtoken.decode('ascii')
}
async with aiohttp.request(
'POST',
'https://oauth2.googleapis.com/token',
data=data,
raise_for_status=True
) as response:
json_body = await response.json()
return json_body['access_token'], json_body["expires_in"]
async def main():
async with AsyncSMTPClient(
host='smtp.gmail.com', authenticator=authenticator
) as client:
await client.send_message(message)
# Your gmail user name
me = 'my.name@gmail.com'
# Service account ID and private key – these have to be obtained from Gmail
client_id = 'yourserviceaccount@yourdomain.iam.gserviceaccount.com'
private_key = '-----BEGIN PRIVATE KEY-----\n...-----END PRIVATE KEY-----\n'
authenticator = GMailAuthenticator(
username=me, client_id=client_id, private_key=private_key
)
# The message you want to send
message = EmailMessage()
message['From'] = me
message['To'] = 'somebody@somewhere'
message['Subject'] = 'Test from smtpproto'
message.set_content('This is a test.')
# Actually sends the message by running main()
anyio.run(main)
Sending mail via Office 365
Warning
It is currently not clear what actual permissions the service account requires. As such, this example should work but has never been successfully tested.
The following example assumes the presence of a registered Azure application
authorized to send email via SMTP (using the SMTP.Send
scope). It uses the
device code flow to obtain an access token.
In order for the device code flow to work for the registered application, the following settings must be in place:
The redirect URI for the application must be
https://login.microsoftonline.com/common/oauth2/nativeclient
The
Treat application as a public client
option must be enabledThe
SMTP.Send
permission fromMicrosoft Graph
must be added in the configured permissions
In addition, your Azure AD must not have Security defaults enabled.
from email.message import EmailMessage
import aiohttp
import anyio
from smtpproto.auth import OAuth2Authenticator
from smtpproto.client import AsyncSMTPClient
class AzureAuthenticator(OAuth2Authenticator):
def __init__(
self, username: str, tenant_id, client_id: str, client_secret: str
):
super().__init__(username)
self.tenant_id = tenant_id
self.client_id = client_id
self.client_secret = client_secret
async def get_token_async(self):
data = {'client_id': self.client_id,
'scope': 'https://outlook.office.com/SMTP.Send',
'client_secret': self.client_secret,
'grant_type': 'client_credentials'}
async with aiohttp.request(
'POST',
f'https://login.microsoftonline.com/{self.tenant_id}/oauth2/v2.0/token',
data=data,
raise_for_status=True
) as response:
json_body = await response.json()
return json_body['access_token'], json_body["expires_in"]
async def main():
async with AsyncSMTPClient(
host='smtp.office365.com', authenticator=authenticator
) as client:
await client.send_message(message)
# Your Office 365 username/email address
me = 'my.name@office365.com'
# Application (client) ID and secret – these have to be obtained from the Azure
# portal
tenant_id = '11111111-1111-1111-1111-111111111111'
client_id = '11111111-1111-1111-1111-111111111111'
client_secret = '...'
authenticator = AzureAuthenticator(
username=me,
tenant_id=tenant_id,
client_id=client_id,
client_secret=client_secret
)
# The message you want to send
message = EmailMessage()
message['From'] = me
message['To'] = 'somebody@somewhere'
message['Subject'] = 'Test from smtpproto'
message.set_content('This is a test.')
# Actually sends the message by running main()
anyio.run(main)