Python smtplib Send Email

Mateen Kiani

Mateen Kiani

Published on Tue Aug 05 2025·4 min read

python-smtplib-send-email

Python’s built-in smtplib library makes sending emails from your scripts a breeze. Yet, many developers rush to sample code without understanding the setup quirks that lead to authentication failures or SSL errors. What if a missing PATH entry or a blocked port is the real culprit behind your script’s silent failures?

By digging into the environment and connection details before crafting your message, you’ll save hours of debugging. In this guide, you’ll learn how to configure SMTP, build a proper email, secure the connection, handle attachments, troubleshoot errors, and optimize for bulk sends. With this knowledge, you’ll write reliable email senders that just work.

Environment Setup

Before you can send a single message, you need Python installed and reachable. If your system doesn’t recognize the python command, add Python to your PATH as explained in add Python to your PATH. A virtual environment also keeps dependencies tidy:

python -m venv venv
source venv/bin/activate # macOS/Linux
venv\Scripts\activate # Windows

Next, install any helpers like email-validator with pip:

pip install email-validator

Check your SMTP server details. For Gmail, use smtp.gmail.com on port 587 (TLS) or 465 (SSL). Corporate servers may require custom ports or firewalls. Always test connectivity with a simple telnet smtp.server.com 587 before scripting.

Tip: If you face blocked ports, try a different network or contact your IT team early.

Crafting the Message

The core of your script is the email object. Python’s email.message.EmailMessage class makes this neat:

from email.message import EmailMessage
msg = EmailMessage()
msg['Subject'] = 'Test Email'
msg['From'] = 'you@example.com'
msg['To'] = 'friend@example.com'
msg.set_content('Hello! This is a test.')

For HTML content, use add_alternative:

html = """<html><body><h1>Hi!</h1></body></html>"""
msg.add_alternative(html, subtype='html')

Keep headers clear:

  • Subject: Short and descriptive
  • From: Your valid email
  • To: Single or comma-separated list

Tip: Validate email addresses with a library to avoid delivery failures.

Secure Connections

SMTP servers often require SSL or TLS. Use TLS (STARTTLS) on port 587 for compatibility:

import smtplib
with smtplib.SMTP('smtp.gmail.com', 587) as server:
server.ehlo()
server.starttls()
server.ehlo()
server.login('you@example.com', 'app_password')
server.send_message(msg)

For SSL on port 465:

with smtplib.SMTP_SSL('smtp.gmail.com', 465) as server:
server.login('you@example.com', 'app_password')
server.send_message(msg)

Tip: Use application-specific passwords instead of your main account password.

Adding Files

Attachments enrich your emails, but you need to prepare:

  1. Create an attachments folder if needed. See mkdir if not exist.
  2. Read the file in binary mode.
  3. Guess the MIME type or set it manually.
  4. Attach using add_attachment.

Example:

import mimetypes
filename = 'report.pdf'
ctype, encoding = mimetypes.guess_type(filename)
maintype, subtype = ctype.split('/', 1)
with open(filename, 'rb') as fp:
msg.add_attachment(fp.read(), maintype=maintype, subtype=subtype, filename=filename)

Tip: For large files, consider streaming or zipping before attaching.

Handling Errors

Email sending can fail silently. Wrap your calls in try/except and log details:

import logging
logging.basicConfig(level=logging.INFO)
try:
server.send_message(msg)
logging.info('Email sent')
except smtplib.SMTPException as e:
logging.error(f'SMTP error: {e}')
except Exception as e:
logging.error(f'Unexpected error: {e}')

Common issues:

  • Authentication failures: Check credentials and app passwords.
  • Connection timeouts: Verify ports and network.
  • Invalid recipients: Catch SMTPRecipientsRefused.

Tip: Increase server.set_debuglevel(1) to see SMTP dialogue.

Multiple Recipients

Sending to a group needs care. You can place multiple addresses in To, Cc, or Bcc:

recipients = ['a@example.com', 'b@example.com']
msg['To'] = ', '.join(recipients)
server.send_message(msg, from_addr, recipients)

For privacy, use Bcc:

msg['Bcc'] = ', '.join(recipients)

Best practices:

  • Personalize messages in loops if content differs.
  • Respect rate limits: add time.sleep() between sends.
  • Use email.utils.make_msgid() for unique message IDs.

Performance Tips

Bulk sending can overwhelm SMTP servers. Optimize:

  • Reuse the SMTP connection instead of reconnecting for each email.
  • Batch sends in groups (e.g., 50 at a time).
  • Implement exponential backoff on failures.
  • Consider asynchronous libraries like aiosmtplib for high throughput.

Example reuse:

with smtplib.SMTP_SSL('smtp.gmail.com', 465) as server:
server.login(user, pwd)
for msg in messages:
server.send_message(msg)

Tip: Monitor send rates to avoid account lockouts.

Conclusion

By mastering smtplib’s setup, message crafting, security layers, and error handling, you’ll transform email sending from guesswork into a reliable feature. Start with a solid environment, build clear messages, secure your connection, and handle attachments and failures gracefully. Whether you’re notifying users, sending reports, or managing bulk newsletters, these practices will save you time and headaches. Now, fire up your code editor, plug in your SMTP details, and send that first email with confidence!

Takeaway: Reliable email automation starts with understanding each step. Apply these tips to build robust, maintainable scripts that just work.


Mateen Kiani
Mateen Kiani
kiani.mateen012@gmail.com
I am a passionate Full stack developer with around 4 years of experience in MERN stack development and 1 year experience in blockchain application development. I have completed several projects in MERN stack, Nextjs and blockchain, including some NFT marketplaces. I have vast experience in Node js, Express, React and Redux.