FAQ¶
Frequently asked questions about python-emails.
How do I send through Gmail / Yandex / other providers?¶
All SMTP providers follow the same pattern — pass the provider’s SMTP
host, port, and credentials in the smtp dict:
response = message.send(
to="recipient@example.com",
smtp={
"host": "<provider SMTP host>",
"port": 587,
"tls": True,
"user": "your-email@example.com",
"password": "your-password-or-app-password"
}
)
Common SMTP settings:
Provider |
Host |
Port |
Encryption |
|---|---|---|---|
Gmail |
|
587 |
|
Yandex |
|
465 |
|
Outlook / Hotmail |
|
587 |
|
Yahoo Mail |
|
465 |
|
Note
Most providers require an app password instead of your regular account password. Consult the provider’s documentation:
Provider settings and authentication requirements change over time. Always refer to the official documentation for up-to-date instructions.
How do I attach a PDF or Excel file?¶
Use attach() with the file’s data and filename.
The MIME type is auto-detected from the filename extension:
# Attach a PDF
message.attach(filename="report.pdf", data=open("report.pdf", "rb"))
# Attach an Excel file
message.attach(filename="data.xlsx", data=open("data.xlsx", "rb"))
# Attach with an explicit MIME type
message.attach(
filename="archive.7z",
data=open("archive.7z", "rb"),
mime_type="application/x-7z-compressed"
)
You can also attach in-memory data:
import io
csv_data = "name,score\nAlice,95\nBob,87\n"
message.attach(
filename="scores.csv",
data=io.BytesIO(csv_data.encode("utf-8"))
)
How is python-emails different from smtplib + email.mime?¶
python-emails is built on top of the standard library’s email and
smtplib modules. The difference is the level of abstraction.
With python-emails:
import emails
from emails.template import JinjaTemplate as T
message = emails.html(
subject=T("Passed: {{ project_name }}#{{ build_id }}"),
html=T("<html><p>Build passed: {{ project_name }} "
"<img src='cid:icon.png'> ...</p></html>"),
text=T("Build passed: {{ project_name }} ..."),
mail_from=("CI", "ci@mycompany.com")
)
message.attach(filename="icon.png", data=open("icon.png", "rb"),
content_disposition="inline")
message.send(
to="somebody@mycompany.com",
render={"project_name": "user/project1", "build_id": 121},
smtp={"host": "smtp.mycompany.com", "port": 587, "tls": True,
"user": "ci", "password": "secret"}
)
The same message with the standard library alone:
import os
import smtplib
from email.utils import formataddr, formatdate, COMMASPACE
from email.header import Header
from email import encoders
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
import jinja2
sender_name, sender_email = "CI", "ci@mycompany.com"
recipient_addr = ["somebody@mycompany.com"]
j = jinja2.Environment()
ctx = {"project_name": "user/project1", "build_id": 121}
html = j.from_string(
"<html><p>Build passed: {{ project_name }} "
"<img src='cid:icon.png'> ...</p></html>"
).render(**ctx)
text = j.from_string("Build passed: {{ project_name }} ...").render(**ctx)
subject = j.from_string(
"Passed: {{ project_name }}#{{ build_id }}"
).render(**ctx)
encoded_name = Header(sender_name, "utf-8").encode()
msg_root = MIMEMultipart("mixed")
msg_root["Date"] = formatdate(localtime=True)
msg_root["From"] = formataddr((encoded_name, sender_email))
msg_root["To"] = COMMASPACE.join(recipient_addr)
msg_root["Subject"] = Header(subject, "utf-8")
msg_root.preamble = "This is a multi-part message in MIME format."
msg_related = MIMEMultipart("related")
msg_root.attach(msg_related)
msg_alternative = MIMEMultipart("alternative")
msg_related.attach(msg_alternative)
msg_text = MIMEText(text.encode("utf-8"), "plain", "utf-8")
msg_alternative.attach(msg_text)
msg_html = MIMEText(html.encode("utf-8"), "html", "utf-8")
msg_alternative.attach(msg_html)
with open("icon.png", "rb") as fp:
msg_image = MIMEImage(fp.read())
msg_image.add_header("Content-ID", "<icon.png>")
msg_related.attach(msg_image)
mail_server = smtplib.SMTP("smtp.mycompany.com", 587)
mail_server.ehlo()
try:
mail_server.starttls()
mail_server.ehlo()
except smtplib.SMTPException as e:
print(e)
mail_server.login("ci", "secret")
mail_server.send_message(msg_root)
mail_server.quit()
The standard library version requires:
Manual MIME tree construction (
MIMEMultipartnesting ofmixed,related, andalternativeparts)Explicit header encoding with
HeaderManual
Content-IDmanagement for inline imagesSeparate template rendering before message assembly
Direct SMTP session management (
ehlo,starttls,login,quit)
python-emails handles all of this internally.
How is python-emails different from django.core.mail?¶
django.core.mail is Django’s built-in email module. It works well
within Django but has several limitations compared to python-emails:
No HTML transformations —
django.core.mailsends HTML as-is.python-emailscan inline CSS, embed images, and clean up unsafe tags viatransform().No template integration — with
django.core.mailyou render templates manually before passing HTML to the message.python-emailsaccepts template objects directly inhtml,text, andsubject.No loaders —
python-emailscan create messages from URLs, ZIP archives, directories, and.emlfiles.No DKIM —
python-emailssupports DKIM signing out of the box.Django-only —
django.core.mailrequires a Django project.python-emailsworks in any Python project.
If you are in a Django project and want to use python-emails,
the DjangoMessage class integrates with Django’s
email backend:
from emails.django import DjangoMessage
message = DjangoMessage(
html="<p>Hello {{ name }}!</p>",
subject="Welcome",
mail_from="noreply@example.com"
)
message.send(to="user@example.com", context={"name": "Alice"})
See the Django Integration section for more details.
How do I debug email sending?¶
There are two levels of debugging: SMTP protocol tracing and Python logging.
SMTP Protocol Trace¶
Set debug=1 in the smtp dict to print the full SMTP conversation
to stdout:
response = message.send(
to="user@example.com",
smtp={"host": "smtp.example.com", "port": 587, "tls": True,
"user": "me", "password": "secret", "debug": 1}
)
This outputs every command and response exchanged with the SMTP server, which is useful for diagnosing authentication failures, TLS issues, and rejected recipients.
Python Logging¶
The library uses Python’s standard logging module. Enable it to see
connection events and retries:
import logging
logging.basicConfig(level=logging.DEBUG)
# Or enable only the emails loggers:
logging.getLogger("emails.backend.smtp.backend").setLevel(logging.DEBUG)
logging.getLogger("emails.backend.smtp.client").setLevel(logging.DEBUG)
Logger names used by the library:
emails.backend.smtp.backend— connection management, retriesemails.backend.smtp.client— SMTP client operations
Inspecting the Message¶
Before sending, you can inspect the raw RFC 822 output:
print(message.as_string())
This shows the full MIME structure, headers, and encoded content — useful for verifying that attachments, inline images, and headers are correct.
Checking the Response¶
After sending, inspect the SMTPResponse object:
response = message.send(to="user@example.com", smtp={...})
print(f"Status: {response.status_code}")
print(f"Text: {response.status_text}")
print(f"Success: {response.success}")
if response.error:
print(f"Error: {response.error}")
if response.refused_recipients:
for addr, (code, reason) in response.refused_recipients.items():
print(f"Refused {addr}: {code} {reason}")