Python Forum
How to attach a file to an email ?
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
How to attach a file to an email ?
#1
Hi

I'm using python 3.8 and Ubuntu 20.04 as shown below :

arbiel@arbiel-NK3S-8-S4:~$ python
Python 3.8.10 (default, Jun 22 2022, 20:18:18) 
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 
arbiel@arbiel-NK3S-8-S4:~$ uname -a
Linux arbiel-NK3S-8-S4 5.15.0-50-generic #56~20.04.1-Ubuntu SMP Tue Sep 27 15:51:29 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
arbiel@arbiel-NK3S-8-S4:~$ 
.

I havn't been able to attach files to emails. I tried both the MIMEText module or the email.message module.

With the MIMEText module, I've been able to send the message, however withoutt any file attached to it.

With the email.message module, I followed two examples I found here https://docs.python.org/3.8/library/emai...t=examples in the python documentation.
The first exampli reads
# Import smtplib for the actual sending function
import smtplib

# Import the email modules we'll need
from email.message import EmailMessage

# Open the plain text file whose name is in textfile for reading.
with open(textfile) as fp:
    # Create a text/plain message
    msg = EmailMessage()
    msg.set_content(fp.read())

# me == the sender's email address
# you == the recipient's email address
msg['Subject'] = f'The contents of {textfile}'
msg['From'] = me
msg['To'] = you

# Send the message via our own SMTP server.
s = smtplib.SMTP('localhost')
s.send_message(msg)
s.quit()
When executing the instruction as "msg['From'] = me" I got this error message "
arbiel@arbiel-NK3S-8-S4:~$ 'test_courriel.py' envoi_emsg

Traceback (most recent call last):
  File "test_courriel.py", line 160, in <module>
    envoi_emsg()
  File "test_courriel.py", line 148, in envoi_emsg
    msg = entête_msg (msg)
  File "test_courriel.py", line 99, in entête_msg
    msg['From'] = tst.exp
  File "/usr/lib/python3.8/email/message.py", line 399, in __setitem__
    max_count = self.policy.header_max_count(name)
AttributeError: 'str' object has no attribute 'header_max_count'
The second example reads :
Import the email modules we'll need
from email.parser import BytesParser, Parser
from email.policy import default

# If the e-mail headers are in a file, uncomment these two lines:
# with open(messagefile, 'rb') as fp:
#     headers = BytesParser(policy=default).parse(fp)

#  Or for parsing headers in a string (this is an uncommon operation), use:
headers = Parser(policy=default).parsestr(
        'From: Foo Bar <[email protected]>\n'
        'To: <[email protected]>\n'
        'Subject: Test message\n'
        '\n'
        'Body would go here\n')

#  Now the header items can be accessed as a dictionary:
print('To: {}'.format(headers['to']))
print('From: {}'.format(headers['from']))
print('Subject: {}'.format(headers['subject']))

# You can also access the parts of the addresses:
print('Recipient username: {}'.format(headers['to'].addresses[0].username))
print('Sender name: {}'.format(headers['from'].addresses[0].display_name))
I got the same error message when I tried to attach the pdf file.

If you want to check on your own system, you can run the following script with two procedure 'envoi_mmt' and 'envoi_emsg'. 'envoi_mmt' sends a email with MIMEText, without attachmemt. 'envoi_emsg' fails with the error "'str' object has no attribute 'header_max_count'".


#!/usr/bin/env python

import os
import sys
import os.path
import smtplib
from email.mime.text import MIMEText
from email.message import EmailMessage
from email.parser import BytesParser, Parser
from email.policy import default

###############################################
# définition des identifiants et mots de passe
###############################################

def expl_tst (exp="[email protected]", dest="[email protected]", url_serveur='142.250.75.229', mdp='0000' ) :
	source = '\n'.join(('#!/usr/bin/env python',
	"exp='"+exp+"'",
	"dest='"+dest+"'",
	"url_serveur='"+url_serveur+"'",
	"mdp='"+mdp+"'",
	""))
	with open (os.path.join(os.path.dirname(sys.argv[0]) + '/tst.py') , 'w') 	as fichier :
		fichier.write(source)
	print(source)

try :
	import tst
except:
	expl_tst()
	print('Update the file ' + os.path.join(os.path.dirname(sys.argv[0]) + '/tst.py') + ' before running this script again.')
	sys.exit(1)
###############################################
# "printat()" permet de tracer le programme par le numéro de la ligne d'appel
###############################################
def printat(*args, **kwargs):
    """Print function with additional line number and filename information.
 
    Adds a string such as "at line 31 in foo.py" to the printed output,
    to indicate the position where the printat() function was called.
 
    All the calls to print() in a program can be changed
    to provide additional information by adding
 
        print = printat
 
    at the top of the program.
    """
    import os, sys
    level = kwargs.pop('level', 0)
    frame = sys._getframe(level+1)
    try:
        lineno, code = frame.f_lineno, frame.f_code
        args += (f'at line {lineno} in {os.path.basename(code.co_filename)}',)
    finally:
        del frame
    print(*args, **kwargs)
    
###############################################
# Création de la pièce à joindre au message et du corps du message
############################################### 

def corps_courriel () :
	printat()
	return """Bonjour Monsieur,

Je vous prie de trouver ci-joint le projet de compte rendu de l'assemblée générale des actionnaires de votre société.

Je vous confirme mon accord pour vous rencontrer dans vos locaux le 4 juillet prochain pour en discuter et recueillir vos remarques.

En vous souhaitant bonne réception de ce rapport,

Sincères salutations.

Jean Quidam
"""
from xhtml2pdf import pisa	
def enregistre_pdf ():
	printat()
	output_filename = '/tmp/test_courriel.pdf'
	result_file = open(output_filename, "w+b")
	# convert HTML to PDF
	pisa_status = pisa.CreatePDF(
		f_html (),                # the HTML to convert
		dest=result_file)           # file handle to recieve result
	# close output file
	result_file.close()
	# close output file
	# return False on success and True on errors
	return output_filename
	
def f_html () :
	printat()
	return """<!doctype html>
<html lang="fr" class="no-js">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport"
            content="initial-scale=1.0, width=device-width, viewport-fit=cover">
</head><body>
<p>
	À Montpellier, ville historiquement acquise à la gauche et réputée comme gay-friendly, le phénomène est nouveau. Ces dernières années, un groupe de gros bras néonazis multiplie les attaques. Leur visibilité et la violence de leurs actions, assumée sur les réseaux sociaux la plupart du temps, sont allées crescendo depuis 2022.
</p></body></html>	"""
	
###############################################
# Envoi du message avec MIMEText
############################################### 
def envoi_mmt() :
	printat()
	texte_message = corps_courriel ()
	msg = MIMEText (texte_message)
	msg['From'] = tst.exp
	msg['To'] = tst.dest
	msg['Subject'] = "Projet de compte rendu de l'assemblée générale"
	msg['Attachment'] = enregistre_pdf()
	mailServer = smtplib.SMTP(tst.url_serveur, 1025)
	ehlo = mailServer.ehlo()
	print(ehlo)
	# 4. Of course, we use a secure connection
	mailServer.starttls()
	mailServer.ehlo()
	s = mailServer.login(tst.exp, tst.mdp)
	printat()
	print(s)
	print(msg)
#	mailServer.sendmail(expediteur, destinataire, msg.as_string())
	print(mailServer.send_message(msg, from_addr='<[email protected]>'))
	mailServer.close()

def envoi_emsg () :
	msg = EmailMessage(corps_courriel ())
	exp = 'From: '+ tst.exp.join(('<','>'))
	dest = 'To: '+ tst.dest.join(('<','>'))
	objet = "Subject: Projet de compte rendu de l'assemblée générale"
	headers = Parser(policy=default).parsestr('\n'.join((exp, dest, objet,'',corps_courriel (),'')))
	fichier=enregistre_pdf()
	with open(fichier, 'rb') as fp:
		msg.add_attachment(fp.read(),maintype='application',subtype='pdf', filename=fichier)
	mailServer = smtplib.SMTP(tst.url_serveur, 1025)
	ehlo = mailServer.ehlo()
	mailServer.starttls()
	mailServer.ehlo()
	s = mailServer.login(tst.exp, tst.mdp)
#	print(msg)
	print(s)
	print(tst.exp.join(('<','>')))
#	mailServer.send_message(msg, from_addr=tst.exp.join(('<','>')))
	print(mailServer.send_message(msg, from_addr='<[email protected]>'))
	mailServer.close()
	printat()
	
if __name__ == '__main__':
	code = sys.argv[1]
	if code == 'envoi_mmt' :
		envoi_mmt()
	if code == 'envoi_emsg' :
		envoi_emsg()
	if code == 'expl_tst' :
		expl_tst(exp='[email protected]', dest='[email protected]', url_serveur='127.0.0.1', mdp='7phnuP6EP7npE5w-b6haDg')
	sys.exit(0)	
Thank's to anybody who will have the time to provide me with some evidence to get out of this difficulty.

Arbiel
using Ubuntu 18.04.4 LTS, Python 3.8
having substituted «https://www.lilo.org/fr/» to google, «https://protonmail.com/» to any other unsafe mail service and bépo to azerty (french keyboard layouts)
Reply
#2
Funnily enough, I was doing this just recently, trying to help my girlfriend send emails to customers.

I am not an expert, but this works with QQ smtp. You will need a so-called App-password, also known as IMAP-password.

gmail is more difficult than QQ. Yahoo will not work at all, even if you do not get an error message!

You may be able to do this with your ordinary email and App-password. If you get an error message "Bad credentials", then you need this:

Do this in bash:

Quote:echo -ne "[email protected]" | base64

which will return something like this:

Quote:cGV0ZXJuYW5qaWdtYWlsLmNvb

This code below works for me on QQ no problem. But if you try and send too many mails the server will complain and stop.

I can send 85 emails no problem, but QQ smtp stopped at 105 emails, so don't send too many at once. Break the receiver list up.

#! /usr/bin/python3
import email, smtplib, ssl
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import formatdate
#from datetime import datetime

subject = "Greetings from China!"
# the email can have a plain text body
body_text = """Hi,
my name is Pedro and I am lazy!
Thank you,
Pedro
"""
# or the email can have an html body, which is formatable
html = """\
<!DOCTYPE html>
<html>
<body style="background-color:gold;"><br><br>
<div style="width:75%; border:4px; border-style:solid; border-color: blueviolet; background-color: silver; border-radius: 20px; padding: 20px; font-size:20px;">
<p>Hi,<br><br>
How are you?<br><br>
My name is <strong>Pedro</strong> and I <strong>make emails</strong><br>
 
You can download our product catalogue from here: <br><br>
<a href="http://www.webpage.com/downloads">Download our product catalogue</a><br><br>

Thank you,<br><br>
Pedro<br><br>
My email: <a href="mailto:[email protected]">Send me an email</a><br><br>

<a href="http://www.webpage.com">Pedro, Bespoke html</a>        
    </p>
 </div>
</body>
</html>
"""

sender_email = "[email protected]"
# better not write your password in the Python
# Use a so-called app-password also called IMAP password: 16 letters like: abcdefghijklmnop
password = input("Type your password and press enter: ")
# Open PDF file in binary mode
# attach a PDF
path2pdf = '/home/pedro/babystuff/forAgro/product list.pdf'
print('Opening pdf ... ')
with open(path2pdf, "rb") as attachment:
    filedata = attachment.read()
names = path2pdf.split('/')
fname = names[-1]
receiver_emails = ["[email protected]", "[email protected]", "[email protected]"] 

def send_email(attach, filename, sender, password, receiver, subject, body, server):    
    message = MIMEMultipart()
    # attach the file
    part = MIMEBase("application", "octet-stream")
    part.set_payload(attach)
    encoders.encode_base64(part)
    part.add_header("Content-Disposition", f"attachment; filename= {filename}",)
    message["From"] = sender
    message["To"] = receiver
    message["Date"] = formatdate(localtime=True)
    message["Subject"] = subject
    message["Bcc"] = receiver  # Recommended for mass emails
    # Add body to email
    message.attach(MIMEText(body, "html"))
    message.attach(part)
    text = message.as_string()
    context = ssl.create_default_context()
    server.sendmail(sender, receiver, text)
    return "OK"

# this worked on QQ smtp, gmail is more difficult, higher security I suppose
port = 465  # or 587 For SL
smtp_server = "smtp.qq.com"
#smtp_server = "smtp.mail.yahoo.com"
context = ssl.create_default_context()
server = smtplib.SMTP_SSL(smtp_server, port, context=context)
# gmail or other mail servers may require the sender and login encoded base64
# in Linux do that in a bash terminal (command line) like this (don't know how in Windows):
# echo -ne "[email protected]" | base64
# which will return something like this:
# cGV0ZXJuYW5qaWdtYWlsLmNvb
# if necessary set sender and password, using your values:
# sender_email = "cGV0ZXJuYW5qaWdtYWlsLmNvb", password = "Z3JoYWF3c3J6cWh0"
# same for your app- password: echo -ne "abcdefghijklmnop" | base64
# QQ smtp server on port 465 works with the email address and IMAP password unencrypted
server.login(sender_email, password)
count = 1
for e in receiver_emails:
    print(f'Sending email {count} to {e} ... ')
    send_email(filedata, fname, sender_email, password, e, subject, html, server)
    print(f'Email {count} sent to {e} ... ')
    count +=1
server.quit()
print('All done!')
Bon chance!
Reply
#3
Hi Pedro

Sorry to be so late to come back and thank you.

In fact, I have'nt been able to run your program successfully.

I made some modifications to adapt it to my context :
I changed the sender and receiver emails and the mail server and the corresponding port. I'n using the so called "Proton bridge" provided by the Proton company. It allows for the end to end mail encryption. The server url is 127.0.0.1 and the port number for smtp is 1025. I want to send my messages through this bridge.

As a result, I got

arbiel@arbiel-NK3S-8-S4:~$ '/home/arbiel/Documents/Documents spécifiques/Communs/Appartements/Λοκατιονς/gestion des quittances/Pedroski55.py' 
Opening pdf ... 
Traceback (most recent call last):
  File "/home/arbiel/Documents/Documents spécifiques/Communs/Appartements/Λοκατιονς/gestion des quittances/Pedroski55.py", line 80, in <module>
    server = smtplib.SMTP_SSL(smtp_server, port, context=context)
  File "/usr/lib/python3.8/smtplib.py", line 1043, in __init__
    SMTP.__init__(self, host, port, local_hostname, timeout,
  File "/usr/lib/python3.8/smtplib.py", line 255, in __init__
    (code, msg) = self.connect(host, port)
  File "/usr/lib/python3.8/smtplib.py", line 339, in connect
    self.sock = self._get_socket(host, port, self.timeout)
  File "/usr/lib/python3.8/smtplib.py", line 1051, in _get_socket
    new_socket = self.context.wrap_socket(new_socket,
  File "/usr/lib/python3.8/ssl.py", line 500, in wrap_socket
    return self.sslsocket_class._create(
  File "/usr/lib/python3.8/ssl.py", line 1040, in _create
    self.do_handshake()
  File "/usr/lib/python3.8/ssl.py", line 1309, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLError: [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1131)
arbiel@arbiel-NK3S-8-S4:~$ 
Have you an idea of how to solve this difficulty ?

Arbiel
using Ubuntu 18.04.4 LTS, Python 3.8
having substituted «https://www.lilo.org/fr/» to google, «https://protonmail.com/» to any other unsafe mail service and bépo to azerty (french keyboard layouts)
Reply
#4
Yeah, I had that problem. Was annoying!

No idea what Proton Bridge is. You don't need help from a company, unless you wish to send thousands of emails.

Connect to the server you want to use, for example smtp.gmail.com or smtp.qq.com on the command line.

As long as you have #! /usr/bin/python3 at the top of your Python script, it will run in bash, on Linux. Not sure how Windows users do this. (Best just install Linux!)

If you are using port 587, try starting this: server.starttls(context=context) and DO NOT use

server = smtplib.SMTP_SSL(smtp_server, port, context=context)
instead use:

server = smtplib.SMTP(smtp_server, port)
Then you can use your ordinary email address and your IMAP password (not your normal password) tls takes care of encryption.

context = ssl.create_default_context()
server = smtplib.SMTP(smtp_server, port)
server.starttls(context=context)
server.login(sender_email, password)
Using port 465, I just tested on QQ. Again you need your IMAP password:

port = 465  # or 587 For SSL
smtp_server = "smtp.qq.com"
#smtp_server = "smtp.mail.yahoo.com"
context = ssl.create_default_context()
server = smtplib.SMTP_SSL(smtp_server, port, context=context)
server.login(sender_email, password)
Sends the attachment to my gmail, no problem

Output:
pedro@pedro-HP:~/babystuff/forAgro/python$ ./send_emails_via_QQ+attachmentv4.py Type your password and press enter: secret_password Opening pdf ... Sending email to [email protected] ... Email sent to [email protected] ... Sending email to [email protected] ... Email sent to [email protected] ... All done! pedro@pedro-HP:~/babystuff/forAgro/python$
Later, I thought, if you are going to send the attachment many times, it is simpler and faster to send a download link in the email.

I modified the little Python script and made 1 Python script for each server, yahoo, gmail and qq They all work OK now!
Reply
#5
To attach a file to an email in Python, you can use the smtplib and email libraries. These libraries help create and send emails with attachments. Below is a step-by-step explanation of the process:

Steps to Attach a File to an Email in Python:

Import Required Libraries

Use the smtplib for sending emails and email for crafting the message with attachments.

Set Up the Email Content

Create an email message with a subject, body text, and the sender/receiver details.

Attach a File

Open the file in binary mode, create a MIME object for the file, and attach it to the email.

Send the Email

Use smtplib.SMTP to connect to an email server (like Gmail), log in, and send the email.

Example Code:
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders

# Email details
sender_email = "[email protected]"
receiver_email = "[email protected]"
password = "your_password"
subject = "Test Email with Attachment"

# Create a MIMEMultipart object
msg = MIMEMultipart()
msg['From'] = sender_email
msg['To'] = receiver_email
msg['Subject'] = subject

# Email body
body = "Please find the attached file."
msg.attach(MIMEText(body, 'plain'))

# File to attach
filename = "example.pdf"  # Change to your file name
file_path = "path/to/example.pdf"  # Change to your file path

# Attach the file
with open(file_path, "rb") as attachment:
    part = MIMEBase('application', 'octet-stream')
    part.set_payload(attachment.read())
    encoders.encode_base64(part)
    part.add_header(
        'Content-Disposition',
        f'attachment; filename={filename}',
    )
    msg.attach(part)

# Connect to the SMTP server and send the email
with smtplib.SMTP('smtp.gmail.com', 587) as server:
    server.starttls()
    server.login(sender_email, password)
    server.send_message(msg)

print("Email sent successfully!")
Key Points:
  • Replace [email protected] and your_password with your credentials.
    Ensure the file path is correct and the file exists.
    Use a secure method (like environment variables) to handle passwords instead of hardcoding them.
buran write Dec-28-2024, 07:25 AM:
Please, use proper tags when post code, traceback, output, etc. This time I have added tags for you.
See BBcode help for more info.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Email newly created file metro17 2 3,112 Aug-29-2019, 07:33 AM
Last Post: metro17

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020