Marc
Marc

Reputation: 183

Gmail Python multiple attachments

I am trying to create a little script that will email multiple attachments using gmail. The code below sends the email but not the attachments. The intended use is to cron a couple db queries and email the results. There will always be 2 files and the file names will be different each day as the date for the report is in the file name. Otherwise I would have just used:

part.add_header('Content-Disposition', 
    'attachment; filename="absolute Path for the file/s"')

Any help greatly appreciated.

import os
import smtplib
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
from email.MIMEImage import MIMEImage
from email.MIMEBase import MIMEBase
from email import Encoders


#Set up crap for the attachments
files = "/tmp/test/dbfiles"
filenames = [os.path.join(files, f) for f in os.listdir(files)]
#print filenames


#Set up users for email
gmail_user = "[email protected]"
gmail_pwd = "somepasswd"
recipients = ['recipient1','recipient2']

#Create Module
def mail(to, subject, text, attach):
   msg = MIMEMultipart()
   msg['From'] = gmail_user
   msg['To'] = ", ".join(recipients)
   msg['Subject'] = subject

   msg.attach(MIMEText(text))

   mailServer = smtplib.SMTP("smtp.gmail.com", 587)
   mailServer.ehlo()
   mailServer.starttls()
   mailServer.ehlo()
   mailServer.login(gmail_user, gmail_pwd)
   mailServer.sendmail(gmail_user, to, msg.as_string())
   # Should be mailServer.quit(), but that crashes...
   mailServer.close()

#get all the attachments
   for file in filenames:
      part = MIMEBase('application', 'octet-stream')
      part.set_payload(open(file, 'rb').read())
      Encoders.encode_base64(part)
      part.add_header('Content-Disposition', 'attachment; filename="%s"'
                   % os.path.basename(file))
      msg.attach(part)
#send it
mail(recipients,
   "Todays report",
   "Test email",
   filenames)

Upvotes: 7

Views: 16903

Answers (3)

tnusraddinov
tnusraddinov

Reputation: 750

I have a similar problem. I am able to send multiple attachments but on my Mac mail app not all attachments appear and html also not appear (on Gmail web all works ok). If someone have the same problem, the code bellow worked for me with python3.8. All attachments and html now appear on mail app.

https://docs.python.org/3/library/email.examples.html#email-examples

little updated:

import os, ssl, sys
import smtplib
# For guessing MIME type based on file name extension
import mimetypes
from email.message import EmailMessage
from email.policy import SMTP
from datetime import datetime
from uuid import uuid4

directory = '/path/to/files'
recipients = ['[email protected]']
sender = '[email protected]'
password = 'password'
email_host, email_port = 'smtp.gmail.com', 465

msg = EmailMessage()
msg['Subject'] = f'This is a subject {datetime.utcnow()}'
msg['To'] = ', '.join(recipients)
msg['From'] = sender
msg.preamble = f'{str(uuid4())}\n'

msg.add_alternative('This is a PLAIN TEXT', subtype='plain')
msg.add_alternative('''\
    <html>
    <head></head>
    <body>
        <h4>Hello World! this is HTML </h4>
        <p style="margin: 0;">Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
        tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
        quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
        consequat.</p>
    </body>
    </html>''', subtype='html')

for filename in ['sample.pdf', 'sample2.pdf']:
    path = os.path.join(directory, filename)
    if not os.path.isfile(path):
        continue
    # Guess the content type based on the file's extension.  Encoding
    # will be ignored, although we should check for simple things like
    # gzip'd or compressed files.
    ctype, encoding = mimetypes.guess_type(path)
    if ctype is None or encoding is not None:
        # No guess could be made, or the file is encoded (compressed), so
        # use a generic bag-of-bits type.
        ctype = 'application/octet-stream'
    maintype, subtype = ctype.split('/', 1)
    with open(path, 'rb') as fp:
        msg.add_attachment(fp.read(),
                            maintype=maintype,
                            subtype=subtype,
                            filename=filename)

context = ssl.create_default_context()
with smtplib.SMTP_SSL(email_host, email_port, context=context) as server:
    server.login(sender, password)
    server.send_message(msg)




Upvotes: 0

Dror Paz
Dror Paz

Reputation: 387

Thanks @marc! I can't comment on your answer, so here are few fixes (misnamed variables) and small improvements:

import os
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email import MIMEImage
from email.mime.base import MIMEBase
from email import Encoders

def mail(to, subject, text, attach):
    # allow either one recipient as string, or multiple as list
    if not isinstance(to,list):
        to = [to]
    # allow either one attachment as string, or multiple as list
    if not isinstance(attach,list):
        attach = [attach]

    gmail_user='[email protected]'
    gmail_pwd = "password"
    msg = MIMEMultipart()
    msg['From'] = gmail_user
    msg['To'] = ", ".join(to)
    msg['Subject'] = subject

    msg.attach(MIMEText(text))

    #get all the attachments
    for file in attach:
        print file
        part = MIMEBase('application', 'octet-stream')
        part.set_payload(open(file, 'rb').read())
        Encoders.encode_base64(part)
        part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(file))
        msg.attach(part)

    mailServer = smtplib.SMTP("smtp.gmail.com", 587)
    mailServer.ehlo()
    mailServer.starttls()
    mailServer.ehlo()
    mailServer.login(gmail_user, gmail_pwd)
    mailServer.sendmail(gmail_user, to, msg.as_string())
    # Should be mailServer.quit(), but that crashes...
    mailServer.close()

if __name__ == '__main__':
    mail(['recipient1', 'recipient2'], 'subject', 'body text',
         ['attachment1', 'attachment2'])

Upvotes: 2

Marc
Marc

Reputation: 183

Should have waited another hour before posting. Made 2 changes:

1.) moved the attachment loop up

2.) swapped out part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(file))

for part.add_header('Content-Disposition', 'attachment; filename="%s"' % file)

Works like a champ. Gmail with multiple recipients and multiple attachments.

import os 
import smtplib
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
from email.MIMEImage import MIMEImage
from email.MIMEBase import MIMEBase
from email import Encoders


#Set up crap for the attachments
files = "/tmp/test/dbfiles"
filenames = [os.path.join(files, f) for f in os.listdir(files)]
#print filenames


#Set up users for email
gmail_user = "[email protected]"
gmail_pwd = "somepasswd"
recipients = ['recipient1','recipient2']

#Create Module
def mail(to, subject, text, attach):
   msg = MIMEMultipart()
   msg['From'] = gmail_user
   msg['To'] = ", ".join(recipients)
   msg['Subject'] = subject

   msg.attach(MIMEText(text))

   #get all the attachments
   for file in filenames:
      part = MIMEBase('application', 'octet-stream')
      part.set_payload(open(file, 'rb').read())
      Encoders.encode_base64(part)
      part.add_header('Content-Disposition', 'attachment; filename="%s"' % file)
      msg.attach(part)

   mailServer = smtplib.SMTP("smtp.gmail.com", 587)
   mailServer.ehlo()
   mailServer.starttls()
   mailServer.ehlo()
   mailServer.login(gmail_user, gmail_pwd)
   mailServer.sendmail(gmail_user, to, msg.as_string())
   # Should be mailServer.quit(), but that crashes...
   mailServer.close()

#send it
mail(recipients,
   "Todays report",
   "Test email",
   filenames)

Upvotes: 10

Related Questions