Automate Python emails with attachments that get delivered
Some of the most important emails require attachments, such as receipts, order confirmations, contracts, or digital assets. You need those emails to arrive automatically and reliably when customers and partners expect them.
But attachments tend to increase bounce rates due to either security concerns, reduced engagement signals, or inbox provider policies, making deliverability harder to guarantee.
A few key practices help you get top-tier deliverability when email attachments really matter:
- Follow general email guidelines
- Understand where problems happen and how to spot them
- Create processes that put best practices in motion
- Automate to scale your success for a higher volume
Below, we’ll show you how to implement these practices in a Python application.
Why emails with attachments fail delivery
Email bounces happen for a host of reasons. If you send attachments and see bounces, those files are likely causing delivery failures.
Attachments pose security risks #
Phishing and malware attacks often use attachments, so inbox providers scrutinize them closely:
- Providers automatically block anything executable or compressed (e.g., .7z, .rar, .exe). This policy often extends to .zip files.
- Providers see red flags when you send unsolicited attachments, especially from new domains, even for “safe” file types like PDFs.
Attachments affect sender reputation #
Recipients tend to avoid opening messages with unexpected attachments. Lower open rates, in turn, harm your domain reputation, making it even harder to deliver your emails.
Improving your deliverability starts with digging into exactly where you’re having problems. The first resource to consult is SMTP bounce messages.
Study your bounce messages
If your emails regularly bounce back, it’s fair to assume the underlying issue is the file attachments. You can find out more by examining the error reports.
The Postmark dashboard helps you make sense of errors. If you don’t have the tool set up, you can look into the bounce messages in your sending inbox.
This SMTP error message shows what you might see when an attachment causes an email to bounce:
From: Mail Delivery System MAILER-DAEMON@recipient-isp.com
To: sender@yourdomain.com
Date: Wednesday, Apr 22, 2026, 3:28 PM
Subject: Undeliverable: Project Files Update
Delivery has failed to these recipients or groups:
recipient@example.com
Your message was rejected by the recipient's email system because it was flagged as a security risk or spam.
Diagnostic information for administrators:
Generating server: example.com
recipient@example.com
#554 5.7.1 Service unavailable; Message rejected: Attachment content triggered security policy (SPAM-FILTER-ATTACHMENT) ##
Original message headers:
Return-Path: sender@yourdomain.com
Received: from yourdomain.com (192.168.1.100) by example.com
(10.0.0.1) with Microsoft SMTP Server id 15.1.2507.35; Wed, 22 Apr 2026 15:27:55 -0700
Subject: Project Files Update
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="--boundary_12345"
----boundary_12345
Content-Type: application/x-zip-compressed; name="invoice_details.zip"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="invoice_details.zip"
At a high level, this report shows that the inbox provider flagged your email as a security risk. The specific SMTP error code 554 5.7.1 indicates that your attachment triggered the inbox security policy. The attached file is a .zip — compressed files are more likely to cause problems than some other attachment types.
Error code 554 often indicates a problem with attachments. Depending on the security policies your inbox providers use, you might see the following errors.
| Code | Message | Meaning |
| 450 | Requested mail action not taken: mailbox unavailable. | Temporary bounce, often due to rate limiting or “greylisting” because of suspicious behavior (including attachments). Retry. |
| 550 | Requested action not taken: mailbox unavailable. | Permanent rejection, otherwise similar to 450. Non-specific, but attachments a likely cause if sent. |
| 550 5.7.1 | Policy/security rejection | Email violated a security policy. Attachment content is a likely cause. |
| 552 | Requested mail action aborted: exceeded storage allocation. | Attachment and/or message size are too large to be stored, or mailbox is full. |
| 552 5.3.4 | Message too big: The attachment exceeds the server's per-message size limit. | Total size of message and attachments is too large. |
| 552 5.6.2 | Attachment too large | Attachment itself is too large. |
| 552 5.7.1 | Policy violation: Message blocked due to spam-related content or security policies. | Attachment size and content/format both triggered the bounce. |
| 554 | Transaction has failed | Message rejected. Attachments a likely cause if sent. |
| 554 5.6.0 | Unsupported content | Attachment type is banned by policy. |
| 554 5.7.1 | Security policy violation | Attachment flagged as potential threat. |
Different error codes can indicate the same problems — attachments that are too large or that raise security red flags due to content or file type.
Secure delivery for your attachments, every time #
Are we suggesting you stop sending attachments? Of course not. After all, some attachments must be sent. So how do you ensure delivery on those critical files?
Let’s review best practices for sending automated attachments, then the basics of implementing them in a Python application.
Best practices for sending attachments #
Regardless of what your bounce messages show, there are three hard rules:
- Follow size limits: Inbox provider policies vary, so scope all your emails to the smallest allowed file sizes:
- Keep `TextBody` and `HtmlBody` under 5 MB each.
- Limit your total message size, including attachments, to 10 MB.
- Be conservative with size limits, because encryption increases file size. A 15 MB attachment can easily grow to over 20 MB after base64 encoding.
- Send only allowed file types:
- PDFs are the most widely allowed file type.
- Standard file types like .docx, .xlsx, .pptx, .csv, .txt, .rtf, .jpg, .png, .bmp, .mp3/4, .wav, and .eml are also widely allowed.
- Note that older Microsoft Office filetypes (.doc, xls, ppt) are often flagged by many corporate email gateways or security tools.
- Check your email API to see which attachment types they allow. Stricter limits help maintain deliverability.
- Avoid red flags:
- Executable files (.exe, .bat, .sh, .scr, and .vbs) get your emails blocked. Nearly 90% of binary files in email are malicious. Scripts, including .vbs files, aren’t as commonly abused, but are still not a good risk.
- Avoid HTML files, the most commonly weaponized file type.
- QR codes in an email body or attachment can disguise malicious links. 68% of malicious PDFs now contain QR codes. Inbox providers increasingly block them all out of caution.
- Skip .svg files, which are used for a growing number of malicious attacks. In fact, .svg attacks increased 50-fold in 2025.
Best practices for attachments include “softer” guidelines, as well. You may need to adapt these, but following them closely improves delivery rates.
- Send attachments sparingly. Only send information in an attachment when that’s truly the best format:
- If the content fits in the email body without too much formatting, put it there. Use tested templates where possible.
- Send event information in body text or a secure invitation link, as .ics files are a growing tool for phishing attacks.
- Spam filters find it easier to parse embedded images, so opt for these over small .jpg or other image file attachments.
- Reserve attachments for critical documents such as invoices, detailed receipts, and contracts, or for digital assets such as transparent image files.
- Maintain a high sender reputation score for any mail server sending attachments. Use a dedicated server for transactional emails and send attachments from that domain:
- Don’t send attachments to bulk mailing lists or from bulk email servers. Keep attachments out of marketing emails.
- Only send attachments that recipients are highly likely to open. They won’t click on unsolicited attachments (and they shouldn’t!).
- Use a file format that’s easy to open and hard to exploit. You need your attachments to be delivered and opened to keep your reputation score high:
- Use PDF files whenever possible. Standard document formats like .doc(x), .xls(s), and .ppt(x) are no less safe than PDFs, but PDFs are more deliverable:
- Outdated inbox security policies may still treat PDFs as more secure than other document formats.
- PDFs are a universal open standard, so all recipients can open them.
- For image files, use .jpg or .png if you need transparency.
- Avoid sending compressed files. If you must, use .zip.
- Use PDF files whenever possible. Standard document formats like .doc(x), .xls(s), and .ppt(x) are no less safe than PDFs, but PDFs are more deliverable:
- Use secure links for larger or executable files. Transactional email recipients already have a relationship with you, so you can ask them to log in to access valuable files. Keep the most sensitive information out of email and store it where you have more control over access.
These guidelines help you deliver your most important files successfully. Next, we’ll show you the basics of how to do this in a Python application.
Use built-in Python libraries to send attachments #
The code sample below shows the most basic way to send an email with an attachment. It uses two built-in Python modules to send a ready .pdf file.
import smtplib
from email.message import EmailMessage
# Create the email
msg = EmailMessage()
msg['Subject'] = 'Receipt Attachment'
msg['From'] = 'your_email@example.com'
msg['To'] = 'recipient@sample.com'
msg.set_content('Hello,\n\nThis is a test email with an attached receipt.\n')
# Attach a file
file_path = 'example_receipt.pdf'
with open(file_path, 'rb') as f:
file_data = f.read()
file_name = f.name
msg.add_attachment(file_data, maintype='application', subtype='pdf', filename=file_name)
# Send the email via SMTP server
smtp_server = 'smtp.example.com'
smtp_port = 587
with smtplib.SMTP(smtp_server, smtp_port) as server:
server.starttls() # Secure the connection
server.login('your_email@example.com', 'your_password')
server.send_message(msg)
print("Email sent successfully!")
This code is a bare-bones way of sending email. You can integrate it into an automated workflow triggered by an application event.
Automate for efficiency and consistency #
You already know that you should only send essential attachments automatically — receipts, invoices, and contracts, all documents your users expect.
The automatic trigger for these messages is user action in your application. In this scenario, human involvement can derail efficiency. Design, test, and monitor your process carefully, then let it run hands-off to ensure delivery.
Here’s a simple automation script that takes data from an in-application event to create an attachment and send an email:
import smtplib
from email.message import EmailMessage
import os
def generate_receipt(order):
"""Create a simple text receipt file."""
filename = f"receipt_{order['order_id']}.txt"
with open(filename, 'w') as f:
f.write(f"Order ID: {order['order_id']}\n")
f.write(f"Customer: {order['customer_name']}\n")
f.write(f"Email: {order['customer_email']}\n\n")
f.write("Items:\n")
total = 0
for item in order['items']:
line_total = item['price'] * item['quantity']
total += line_total
f.write(f"- {item['name']} (x{item['quantity']}): ${line_total:.2f}\n")
f.write(f"\nTotal: ${total:.2f}\n")
return filename
def send_email_with_attachment(to_email, subject, body, reply_to, attachment_path):
"""Send email with attachment."""
msg = EmailMessage()
msg['Subject'] = subject
msg['From'] = 'your_email@example.com'
msg['Reply-To'] = reply_to
msg['To'] = to_email
msg.set_content(body)
# Attach file
with open(attachment_path, 'rb') as f:
file_data = f.read()
file_name = os.path.basename(f.name)
msg.add_attachment(file_data, maintype='application', subtype='octet-stream', filename=file_name)
# Send email
with smtplib.SMTP('smtp.example.com', 587) as server:
server.starttls()
server.login('your_email@example.com', 'your_password')
server.send_message(msg)
def process_purchase(order):
"""Simulates handling a completed purchase."""
print(f"Processing order {order['order_id']}...")
# Step 1: Generate receipt
receipt_file = generate_receipt(order)
# Step 2: Send confirmation email
subject = f"Your Order Confirmation #{order['order_id']}"
body = f"""Hi {order['customer_name']},
Thank you for your purchase! Your order has been processed successfully.
Please find your receipt attached.
Best regards,
Your Store Team
"""
send_email_with_attachment(
order['customer_email'],
subject,
body,
reply_to='orders@example.com',
receipt_file
)
# Optional: clean up the receipt file
os.remove(receipt_file)
print("Order processed and email sent.")
# ---- Example usage (simulated purchase event) ----
if __name__ == "__main__":
sample_order = {
"order_id": "12345",
"customer_name": "Jane Doe",
"customer_email": "customer@sample.com",
"items": [
{"name": "T-shirt", "price": 19.99, "quantity": 2},
{"name": "Hat", "price": 14.99, "quantity": 1}
]
}
# This simulates the trigger after a successful checkout/payment
process_purchase(sample_order)
Automating receipt creation and message sending means you know exactly what you sent and when. This approach helps you avoid sending a bad file type, as attachments always go out from the same mail server.
In theory, this could create an attachment that’s too large, but it’s extremely unlikely. You could add a validation method to address that concern if your application calls for it.
Notice that the email uses a `reply_to` field in the header. It’s a good idea to set up an inbox for inbound customer emails. The mailbox you use for automated sending will receive a lot of SMTP reports and other automated messages, and no-reply addresses often set off spam filters.
One final adjustment can make your automated messages more efficient. Larger messages with attachments slow servers and delay delivery. Send attachments as a background job instead of sending messages synchronously in `process_purchase`. In production, use a third-party tool to handle message queues.
Next-level deliverability for Python email with Postmark #
The best way to improve attachment deliverability is to use an email service with a strong track record. Postmark makes deliverability affordable and accessible, in part by maintaining completely separate services for transactional and bulk email.
In even better news for Python developers, Postmark recently introduced a new Python library. Simply import it into your application with the commands `pip install postmark-python` and `import postmark`.
The package gives you access to templates that help you reduce the number of attachments you send, and built-in checks to ensure the attachments and messages you do send are compatible with inbox security policies.

