Send email in Python conveniently for gmail using yagmail
Python
2,739
319 commits
updated May 26, 2026
An asynchronous asyncio version is built-in (see Asynchronous Client below).
The goal here is to make it as simple and painless as possible to send emails.
In the end, your code will look something like this (an async version is also available below):
import yagmail
yag = yagmail.Client('mygmailusername', 'mygmailpassword')
contents = ['This is the body, and here is just text http://somedomain/image.png',
'You can find an audio file attached.', '/local/path/song.mp3']
yag.send('to@someone.com', 'subject', contents)
For Gmail, use an Application-Specific Password as the password — regular account passwords no longer work over SMTP. If you want revocable, scope-limited credentials, use OAuth2 instead (note: tokens expire after 7 days unless your Google Cloud project is in "In production" status — see below).
| Section | Explanation |
|---|---|
| Install | Find the instructions on how to install yagmail here |
| Start a connection | Get started |
| Usability | Shows some usage patterns for sending |
| Recipients | How to send to multiple people, give an alias or send to self |
| Magical contents | Really easy to send text, html, images and attachments |
| Attaching files | How attach files to the email |
| Asynchronous Client | Send emails asynchronously using asyncio |
| DKIM Support | Add DKIM signature to your emails with your private key |
| Feedback | How to send me feedback |
| Roadmap (and priorities) | Yup |
| Errors | List of common errors for people dealing with sending emails |
pip install yagmail[all]
As a side note, yagmail can now also be used to send emails from the command line.
yagmail.Client is the preferred class to instantiate (the legacy name yagmail.SMTP is kept as a backward-compatible alias):
yag = yagmail.Client('mygmailusername', 'mygmailpassword')
Note that this connection is reusable, closable and when it leaves scope it will clean up after itself in CPython.
As tilgovi points out in #39, SMTP/Client connections do not automatically close in PyPy. The context manager with should be used in that case.
Defining some variables:
to = 'santa@someone.com'
to2 = 'easterbunny@someone.com'
to3 = 'sky@pip-package.com'
subject = 'This is obviously the subject'
body = 'This is obviously the body'
html = '<a href="https://pypi.python.org/pypi/sky/">Click me!</a>'
img = '/local/file/bunny.png'
All variables are optional, and know that not even to is required (you'll send an email to yourself):
yag.send(to = to, subject = subject, contents = body)
yag.send(to = to, subject = subject, contents = [body, html, img])
yag.send(contents = [body, img])
Furthermore, if you do not want to be explicit, you can do the following:
yag.send(to, subject, [body, img])
It is also possible to send to a group of people by providing a list of email strings rather than a single string:
yag.send(to = to)
yag.send(to = [to, to2]) # List or tuples for emailadresses *without* aliases
yag.send(to = {to : 'Alias1'}) # Dictionary for emailaddress *with* aliases
yag.send(to = {to : 'Alias1', to2 : 'Alias2'}
Giving no to argument will send an email to yourself. In that sense, yagmail.Client().send() can already send an email.
Be aware that if no explicit to = ... is used, the first argument will be used to send to. Can be avoided like:
yag.send(subject = 'to self', contents = 'hi!')
Note that by default all email addresses are conservatively validated using soft_email_validation==True (default).
It is even safer to use Oauth2 for authentication, as you can revoke the rights of tokens.
This is one of the best sources, upon which the oauth2 code is heavily based.
The code:
yag = yagmail.Client("user@gmail.com", oauth2_file="~/oauth2_creds.json")
yag.send(subject="Great!")
It will prompt for a google_client_id and a google_client_secret, when the file cannot be found. These variables can be obtained following the previous link.
After you provide them, a link will be shown in the terminal that you should followed to obtain a google_refresh_token. Paste this again, and you're set up!
Note that people who obtain the file can send emails, but nothing else. As soon as you notice, you can simply disable the token.
Your Google Cloud Platform project's OAuth consent screen must be in "In production" publishing status before authorizing to not have the authorization expire after 7 days. See status at https://console.cloud.google.com/apis/credentials/consent
Your OAuth client ID must be of type "Desktop". Check at https://console.cloud.google.com/apis/credentials
For personal use this is all you need — just hit "Publish App" to move the consent screen to production. You'll see an unverified-app warning during the OAuth flow that you can click through; the 7-day expiry no longer applies. Formal Google verification (and the annual CASA security assessment required for the restricted https://mail.google.com/ scope yagmail uses) is only needed if you want to remove that warning for external users.
contentsThe contents argument will be smartly guessed. It can be passed a string (which will be turned into a list); or a list. For each object in the list:
<h1>This is a big title</h1>Note that local files can be html (inline); everything else will be attached.
Local files require to have an extension for their content type to be inferred.
As of version 0.4.94, raw and inline have been added.
raw ensures a string will not receive any "magic" (inlining, html, attaching)inline will make an image appear in the text.There are multiple ways to attach files in the attachments parameter (in addition to magical contents parameter).
yag.send(to=recipients,
subject=email_subject,
contents=contents,
attachments=['path/to/attachment1.png', 'path/to/attachment2.pdf', 'path/to/attachment3.zip']
)
io.IOBase.with open('path/to/attachment', 'rb') as f:
yag.send(to=recipients,
subject=email_subject,
contents=contents,
attachments=f
)
In this example f is an instance of _io.BufferedReader a subclass of the abstract class io.IOBase.
f has in this example the attribute .name, which is used by yagmail as filename as well as to detect the correct MIME-type.
Not all io.IOBase instances have the .name attribute in which case yagmail names the attachments attachment1, attachment2, ... without a file extension!
Therefore, it is highly recommended setting the filename with extension manually e.g. f.name = 'my_document.pdf'
A real-world example would be if the attachment is retrieved from a different source than the disk (e.g. downloaded from the internet or uploaded by a user in a web-application)
yagmail includes a built-in asynchronous client (yagmail.AsyncClient, also aliased as yagmail.AsyncSMTP and yagmail.AIOSMTP) using standard library asyncio streams. This provides fully non-blocking asynchronous email sending without external dependencies.
import asyncio
import yagmail
async def main():
# Use AsyncClient as an async context manager
async with yagmail.AsyncClient('mygmailusername', 'mygmailpassword') as yag:
contents = ['This is the body of the async email.', '/local/path/song.mp3']
await yag.send('to@someone.com', 'subject', contents)
asyncio.run(main())
To send emails with dkim signature, you need to install the package with all related packages.
pip install yagmail[all]
# or
pip install yagmail[dkim]
Usage:
from yagmail import Client
from yagmail.dkim import DKIM
from pathlib import Path
# load private key from file/secrets manager
private_key = Path("privkey.pem").read_bytes()
dkim_obj = DKIM(
domain=b"a.com",
selector=b"selector",
private_key=private_key,
include_headers=[b"To", b"From", b"Subject"],
# To include all default headers just pass None instead
# include_headers=None,
)
yag = Client(dkim=dkim_obj)
# all the rest is the same
I'll try to respond to issues within 24 hours at Github.....
And please send me a line of feedback with Client().feedback('Great job!') :-)
yagmail as a command on CLI upon installfeedback function on SMTP to be able to send me feedback directly :-)raw and inline.yagmail file to contain more parameters (medium)smtplib.SMTPException: SMTP AUTH extension not supported by server
SMTPAuthenticationError: Application-specific password required
YagAddressError: This means that the address was given in an invalid format. Note that From can either be a string, or a dictionary where the key is an email, and the value is an alias {'sample@gmail.com': 'Sam'}. In the case of 'to', it can either be a string (email), a list of emails (email addresses without aliases) or a dictionary where keys are the email addresses and the values indicate the aliases.
YagInvalidEmailAddress: Note that this will only filter out syntax mistakes in emailaddresses. If a human would think it is probably a valid email, it will most likely pass. However, it could still very well be that the actual emailaddress has simply not be claimed by anyone (so then this function fails to devalidate).
Click to enable the email for being used externally https://www.google.com/settings/security/lesssecureapps
Make sure you have a working internet connection
If you get an ImportError try to install with sudo, see issue #13
If you like yagmail, feel free (no pun intended) to donate any amount you'd like :-)
Overall
Maintained
Code review
PyPI
Latest release
Releases this year
617 followers · starred Jul 2024
25 followers · starred Feb 2022
103 followers · starred Aug 2015
93 followers · starred Aug 2015
Send email in Python conveniently for gmail using yagmail
Python
2,739
319 commits
updated May 26, 2026
An asynchronous asyncio version is built-in (see Asynchronous Client below).
The goal here is to make it as simple and painless as possible to send emails.
In the end, your code will look something like this (an async version is also available below):
import yagmail
yag = yagmail.Client('mygmailusername', 'mygmailpassword')
contents = ['This is the body, and here is just text http://somedomain/image.png',
'You can find an audio file attached.', '/local/path/song.mp3']
yag.send('to@someone.com', 'subject', contents)
For Gmail, use an Application-Specific Password as the password — regular account passwords no longer work over SMTP. If you want revocable, scope-limited credentials, use OAuth2 instead (note: tokens expire after 7 days unless your Google Cloud project is in "In production" status — see below).
| Section | Explanation |
|---|---|
| Install | Find the instructions on how to install yagmail here |
| Start a connection | Get started |
| Usability | Shows some usage patterns for sending |
| Recipients | How to send to multiple people, give an alias or send to self |
| Magical contents | Really easy to send text, html, images and attachments |
| Attaching files | How attach files to the email |
| Asynchronous Client | Send emails asynchronously using asyncio |
| DKIM Support | Add DKIM signature to your emails with your private key |
| Feedback | How to send me feedback |
| Roadmap (and priorities) | Yup |
| Errors | List of common errors for people dealing with sending emails |
pip install yagmail[all]
As a side note, yagmail can now also be used to send emails from the command line.
yagmail.Client is the preferred class to instantiate (the legacy name yagmail.SMTP is kept as a backward-compatible alias):
yag = yagmail.Client('mygmailusername', 'mygmailpassword')
Note that this connection is reusable, closable and when it leaves scope it will clean up after itself in CPython.
As tilgovi points out in #39, SMTP/Client connections do not automatically close in PyPy. The context manager with should be used in that case.
Defining some variables:
to = 'santa@someone.com'
to2 = 'easterbunny@someone.com'
to3 = 'sky@pip-package.com'
subject = 'This is obviously the subject'
body = 'This is obviously the body'
html = '<a href="https://pypi.python.org/pypi/sky/">Click me!</a>'
img = '/local/file/bunny.png'
All variables are optional, and know that not even to is required (you'll send an email to yourself):
yag.send(to = to, subject = subject, contents = body)
yag.send(to = to, subject = subject, contents = [body, html, img])
yag.send(contents = [body, img])
Furthermore, if you do not want to be explicit, you can do the following:
yag.send(to, subject, [body, img])
It is also possible to send to a group of people by providing a list of email strings rather than a single string:
yag.send(to = to)
yag.send(to = [to, to2]) # List or tuples for emailadresses *without* aliases
yag.send(to = {to : 'Alias1'}) # Dictionary for emailaddress *with* aliases
yag.send(to = {to : 'Alias1', to2 : 'Alias2'}
Giving no to argument will send an email to yourself. In that sense, yagmail.Client().send() can already send an email.
Be aware that if no explicit to = ... is used, the first argument will be used to send to. Can be avoided like:
yag.send(subject = 'to self', contents = 'hi!')
Note that by default all email addresses are conservatively validated using soft_email_validation==True (default).
It is even safer to use Oauth2 for authentication, as you can revoke the rights of tokens.
This is one of the best sources, upon which the oauth2 code is heavily based.
The code:
yag = yagmail.Client("user@gmail.com", oauth2_file="~/oauth2_creds.json")
yag.send(subject="Great!")
It will prompt for a google_client_id and a google_client_secret, when the file cannot be found. These variables can be obtained following the previous link.
After you provide them, a link will be shown in the terminal that you should followed to obtain a google_refresh_token. Paste this again, and you're set up!
Note that people who obtain the file can send emails, but nothing else. As soon as you notice, you can simply disable the token.
Your Google Cloud Platform project's OAuth consent screen must be in "In production" publishing status before authorizing to not have the authorization expire after 7 days. See status at https://console.cloud.google.com/apis/credentials/consent
Your OAuth client ID must be of type "Desktop". Check at https://console.cloud.google.com/apis/credentials
For personal use this is all you need — just hit "Publish App" to move the consent screen to production. You'll see an unverified-app warning during the OAuth flow that you can click through; the 7-day expiry no longer applies. Formal Google verification (and the annual CASA security assessment required for the restricted https://mail.google.com/ scope yagmail uses) is only needed if you want to remove that warning for external users.
contentsThe contents argument will be smartly guessed. It can be passed a string (which will be turned into a list); or a list. For each object in the list:
<h1>This is a big title</h1>Note that local files can be html (inline); everything else will be attached.
Local files require to have an extension for their content type to be inferred.
As of version 0.4.94, raw and inline have been added.
raw ensures a string will not receive any "magic" (inlining, html, attaching)inline will make an image appear in the text.There are multiple ways to attach files in the attachments parameter (in addition to magical contents parameter).
yag.send(to=recipients,
subject=email_subject,
contents=contents,
attachments=['path/to/attachment1.png', 'path/to/attachment2.pdf', 'path/to/attachment3.zip']
)
io.IOBase.with open('path/to/attachment', 'rb') as f:
yag.send(to=recipients,
subject=email_subject,
contents=contents,
attachments=f
)
In this example f is an instance of _io.BufferedReader a subclass of the abstract class io.IOBase.
f has in this example the attribute .name, which is used by yagmail as filename as well as to detect the correct MIME-type.
Not all io.IOBase instances have the .name attribute in which case yagmail names the attachments attachment1, attachment2, ... without a file extension!
Therefore, it is highly recommended setting the filename with extension manually e.g. f.name = 'my_document.pdf'
A real-world example would be if the attachment is retrieved from a different source than the disk (e.g. downloaded from the internet or uploaded by a user in a web-application)
yagmail includes a built-in asynchronous client (yagmail.AsyncClient, also aliased as yagmail.AsyncSMTP and yagmail.AIOSMTP) using standard library asyncio streams. This provides fully non-blocking asynchronous email sending without external dependencies.
import asyncio
import yagmail
async def main():
# Use AsyncClient as an async context manager
async with yagmail.AsyncClient('mygmailusername', 'mygmailpassword') as yag:
contents = ['This is the body of the async email.', '/local/path/song.mp3']
await yag.send('to@someone.com', 'subject', contents)
asyncio.run(main())
To send emails with dkim signature, you need to install the package with all related packages.
pip install yagmail[all]
# or
pip install yagmail[dkim]
Usage:
from yagmail import Client
from yagmail.dkim import DKIM
from pathlib import Path
# load private key from file/secrets manager
private_key = Path("privkey.pem").read_bytes()
dkim_obj = DKIM(
domain=b"a.com",
selector=b"selector",
private_key=private_key,
include_headers=[b"To", b"From", b"Subject"],
# To include all default headers just pass None instead
# include_headers=None,
)
yag = Client(dkim=dkim_obj)
# all the rest is the same
I'll try to respond to issues within 24 hours at Github.....
And please send me a line of feedback with Client().feedback('Great job!') :-)
yagmail as a command on CLI upon installfeedback function on SMTP to be able to send me feedback directly :-)raw and inline.yagmail file to contain more parameters (medium)smtplib.SMTPException: SMTP AUTH extension not supported by server
SMTPAuthenticationError: Application-specific password required
YagAddressError: This means that the address was given in an invalid format. Note that From can either be a string, or a dictionary where the key is an email, and the value is an alias {'sample@gmail.com': 'Sam'}. In the case of 'to', it can either be a string (email), a list of emails (email addresses without aliases) or a dictionary where keys are the email addresses and the values indicate the aliases.
YagInvalidEmailAddress: Note that this will only filter out syntax mistakes in emailaddresses. If a human would think it is probably a valid email, it will most likely pass. However, it could still very well be that the actual emailaddress has simply not be claimed by anyone (so then this function fails to devalidate).
Click to enable the email for being used externally https://www.google.com/settings/security/lesssecureapps
Make sure you have a working internet connection
If you get an ImportError try to install with sudo, see issue #13
If you like yagmail, feel free (no pun intended) to donate any amount you'd like :-)
Overall
Maintained
Code review
PyPI
Latest release
Releases this year
617 followers · starred Jul 2024
25 followers · starred Feb 2022
103 followers · starred Aug 2015
93 followers · starred Aug 2015