How do I send an email with a .csv attachment using Python
Stefan Bogdanescu
Founder & Senior Architect
How to Send an Email with a .csv Attachment Using Python: A Complete Guide
Sending emails with attachments is a very common requirement in backend development, whether you are building a simple script or a complex application. As a developer, you often need to handle file I/O and network protocols simultaneously. While the basic smtplib module exists, attaching files requires more than just passing a filename; you need to correctly format the email as a MIME (Multipurpose Internet Mail Extensions) message to properly embed the binary data of your CSV file.
If you are new to Python, this process can seem daunting. Let's walk through exactly how to achieve this reliably, moving beyond simple attempts that often fail.
Why Simple Attempts Fail: Understanding MIME
The reason the basic code snippet you encountered doesn't work is that the smtplib module focuses on sending plain text messages. To send binary data like a CSV file as an attachment, you must package that data into a structure called a MIME message. This involves encoding the file content (usually using Base64) so it can be safely transmitted across the email protocol, and then defining the correct boundaries for the attachment within the email body.
In essence, we need to perform three main steps:
- Read the CSV file's binary contents.
- Encode those contents into Base64 text.
- Construct a complete email message that includes this encoded data as an attachment part.
Step-by-Step Guide and Code Implementation
We will use Python’s built-in smtplib for the connection and the email package to handle the complex structuring of the message, which is far more robust than trying to force raw file paths into the sendmail function.
Prerequisites
Before running the code, ensure you have your CSV file ready and that you know your email credentials (e.g., using an App Password if you are using services like Gmail).
The Complete Python Solution
Here is a comprehensive example demonstrating how to read a CSV and attach it to an outgoing email:
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
import base64
# --- Configuration ---
SENDER_EMAIL = "your_email@example.com"
PASSWORD = "your_app_password" # Use an App Password for security
RECEIVER_EMAIL = "recipient@example.com"
SMTP_SERVER = "smtp.gmail.com"
SMTP_PORT = 587
CSV_FILENAME = "data_export.csv"
try:
# 1. Read the CSV file content in binary mode
with open(CSV_FILENAME, 'rb') as f:
csv_data = f.read()
# 2. Encode the data to Base64 for safe transmission
encoded_data = base64.b64encode(csv_data).decode('utf-8')
# 3. Construct the MIME message
msg = MIMEMultipart()
msg['From'] = SENDER_EMAIL
msg['To'] = RECEIVER_EMAIL
msg['Subject'] = "Data Export attached"
# Attach the CSV file as a separate part
part = MIMEBase('application', 'octet-stream')
part.set_payload(encoded_data)
# Write the encoded data to the message
with open("attachment.bin", "wb") as out_file:
out_file.write(part.as_bytes())
# Attach the file part to the main message
part.add_header('Content-Disposition', f'attachment; filename="{CSV_FILENAME}"')
msg.attach(part)
# 4. Send the email via SMTP
server = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
server.starttls() # Secure the connection
server.login(SENDER_EMAIL, PASSWORD)
text = msg.as_string()
server.sendmail(SENDER_EMAIL, RECEIVER_EMAIL, text)
server.quit()
print("Email sent successfully with attachment!")
except FileNotFoundError:
print(f"Error: The file {CSV_FILENAME} was not found.")
except Exception as e:
print(f"An error occurred: {e}")
Best Practices and Developer Insights
As a senior developer, I want to emphasize security and structure. Notice how we moved from trying to use raw sendmail commands to using the dedicated email package. This is crucial because it allows us to correctly handle headers, encoding, and MIME boundaries automatically.
Security Note: Never hardcode passwords directly into production code. For real-world applications, always load sensitive information from environment variables or a secure configuration store. This principle of separating configuration from code is vital in building scalable systems, much like the architectural focus found in modern frameworks like Laravel, where handling data securely and cleanly is paramount.
When dealing with file uploads or attachments on a server, treating the data as streams (binary) rather than simple strings is always the correct approach. This pattern of correctly structuring complex communications is something that developers must master when integrating external services.
Conclusion
Sending an email with a CSV attachment in Python requires understanding how email protocols handle binary data. By leveraging the email package along with Base64 encoding, you can reliably construct a complete MIME message. While it might seem complex initially, mastering this step is fundamental to building robust backend services. Practice separating your concerns—handle file reading separately from network communication—and you will find that even the most complex tasks become straightforward and secure.
Note: Blog content is currently available in English.