Explain Codes LogoExplain Codes Logo

Send HTML emails with Python

python
email-engineering
smtp
smtplib
Alex KataevbyAlex KataevยทJan 13, 2025
โšกTLDR

To send HTML emails with Python, use the smtplib library for SMTP communication and email.mime to craft HTML messages.

Example Code:

import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText # SMTP server configuration (btw, there's no place like 127.0.0.1 ๐Ÿ˜Š) smtp_server, port = "smtp.yourserver.com", 587 username, password = "your_username", "your_password" # remember, sharing is bad... never share your password ๐Ÿ˜„ # Setting up the Email content message = MIMEMultipart("alternative") message["Subject"], message["From"], message["To"] = "HTML Email Test", username, "[email protected]" html = """<html><body><p>Hi, check out <a href="http://www.realpython.com">Real Python</a>.</p></body></html>""" message.attach(MIMEText(html, "html")) # HTML or bust! # Sending the email try: with smtplib.SMTP(smtp_server, port) as server: server.starttls() # Put on some safety goggles as we're going to use SSL! server.login(username, password) # logging in... expect us! server.send_message(message) # Throwing the message in the mailbox ๐Ÿ“ฎ print("Email sent successfully") # A sigh of relief ๐Ÿ˜Œ except Exception as e: print(f"Email could not be sent: {e}") # Reality is often disappointing... ๐Ÿ˜”

Make sure to use TLS connectivity (server.starttls()), handle exceptions for robustness, and never hardcode credentials. Keep them in environment variables or config files instead.

Dealing with HTML contents

Crafting the HTML email body requires creating two content versions: HTML and a plain text fallback. This fallback ensures that recipients using text-only email clients aren't left out.

text_version = "Hi, check out Real Python: http://www.realpython.com" # Don't burn bridges โ€” create a plain text version too! message.attach(MIMEText(text_version, "plain"))

Setting the right email headers

Next, properly set the email headers - they're like the envelope of your email:

message.add_header('Content-Type', 'text/html') # Your email client needs to know that this isn't just any email - it's an HTML email!

Attaching files and SMTP delivery

Who doesn't love attachments? Add them like so:

from email.mime.application import MIMEApplication attachment = MIMEApplication(open('file.pdf', 'rb').read()) # Reading in binary, because PDFs aren't plain text. attachment.add_header('Content-Disposition', 'attachment', filename='file.pdf') # Attach the file with the original filename. message.attach(attachment) # Slap it onto the email.

Don't forget to validate your SMTP configurations and escape HTML special characters. Just like in a zombie apocalypse, security is key.

Ensuring SMTP transactions are handled properly

The SMTP connection allows for smooth mail delivery. You need to start it, use it, and then properly close it - kinda like a good Saturday night.

with smtplib.SMTP(host, port) as server: server.starttls() # Secure the connection. Wrap it up, folks! server.login(username, password) # Maybe the server will slide into your DMs after this. ๐Ÿ™ƒ try: server.send_message(message) # Try to send the email. finally: server.quit() # Always quit... while youโ€™re ahead? ๐Ÿƒ๐Ÿ’จ

Error handling isn't just about catching the error, it's also about letting your users know what went wrong. Not what they want, but what they need. Full transparency as service, please!

Going the Extra Mile

A few pointers when sending HTML emails:

Email Client Compatibility: Test your HTML and CSS on different email clients. Not every client is as forgiving as your mother.

Spam Filters: Watch out for spam filters. They hate fun stuff like large embedded images or multiple links. Tip: Keep your email size reasonable and make sure you have permission to reach out to the recipient.

Responsibility Matters: Use the with statement to manage connections, providing that neat closure you didn't get from your last relationship.

Additional Packages: Some packages like yagmail and py3dns can provide a simpler API experience. Sometimes working smarter results in less head-meet-desk situations.