简体   繁体   中英

python 3.6 gmail api ― send email with attachement

This python 3 script is suppose to creates an email, attach a single file (using it's url) to it and send it. It sends the email, but something goes wrong with the create_message_with_attachment()

TypeError: Attach is not valid on a message with a non-multipart payload

I did read google documentation. The stack threads talking about it focus on fancy attachment styles while mixing up, on the top of it, the different syntax of python version.

The code bellow is a patchwork of several sources. I struggle to join them together in the create_message_with_attachment() .

For instance I don't know if I should include this (it's from create_message_without_attachment() which works on this code. Cf at the bottom)

raw = base64.urlsafe_b64encode(msg.as_bytes())
raw = raw.decode()
body = {'raw': raw}
return body

The create message with attachment code:

import httplib2
import os
import oauth2client
from oauth2client import client, tools
import base64
from email import encoders

#needed for attachment
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

#needed for gmail service
from apiclient import errors, discovery  

#The scope URL for read/write access to the gmail api 
SCOPES = 'https://www.googleapis.com/auth/gmail.send'

CLIENT_SECRET_FILE = 'client_secret.json'
APPLICATION_NAME = 'Gmail API Python Send Email'


def get_credentials():
    # If needed create folder for credential
    home_dir = os.path.expanduser('~') #>> C:\Users\me
    credential_dir = os.path.join(home_dir, '.credentials') # >>C:\Users\me\.credentials   (it's a folder)
    if not os.path.exists(credential_dir):
        os.makedirs(credential_dir)  #create folder if doesnt exist
    credential_path = os.path.join(credential_dir, 'gmail-python-email-send.json')

    #Store the credential
    store = oauth2client.file.Storage(credential_path)
    credentials = store.get()

    if not credentials or credentials.invalid:
    # Create a flow object. (it assists with OAuth 2.0 steps to get user authorization + credentials)
        flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES)
        flow.user_agent = APPLICATION_NAME
        credentials = tools.run_flow(flow, store)
        print('Storing credentials to ' + credential_path)

    return credentials



def SendMessage(sender, to, subject, msgHtml, msgPlain):
    credentials = get_credentials() 

    http = httplib2.Http()  # Create an httplib2.Http object to handle our HTTP requests, and authorize it using credentials.authorize()

    # http is the authorized httplib2.Http() 
    http = credentials.authorize(http)

    service = discovery.build('gmail', 'v1', http=http)

    message_with_attach = create_message_without_attachment(sender, to, subject, msgHtml, msgPlain)
    SendMessageInternal(service, "me", message_with_attach)


def SendMessageInternal(service, user_id, message): 
    try:
        message = (service.users().messages().send(userId=user_id, body=message).execute())  ####need  to get user_id before

        message_ID = message['id']
        print(f'Message Id: {message_ID}')
        return [message, message_ID] #return value as list
    except errors.HttpError as error:
        print(f'An error occurred: {error}')    

def create_message_with_attachment(sender, to, subject, msgHtml, msgPlain):

    # multipart container can contain other MIME parts.  (attachment will be independent of the multipart/alternative)
    msg = MIMEMultipart('alternative')
    msg['To'] = to
    msg['From'] = sender
    msg['Subject'] = subject

    # convert both part to a MIME compatible string
    part1 = MIMEText(msgPlain, 'plain') 
    part2 = MIMEText(msgHtml, 'html')

    # create .txt attachment
    filePath=r"C:\Users\me\Desktop\test_Attachment.txt"
    myFile=open(filePath, "rb")
    attachment= MIMEApplication(myFile.read())
    msg.set_payload(myFile) # 
    myFile.close()
    msg.set_payload(myFile) # 
    myFile.close()

    #This will add a header that looks like: "Content-Disposition: attachment; filename="test_Attachment.txt" "
    attachment.add_header('content-disposition', 'attachment', filename = ('utf-8', '', 'test_Attachment.txt'))

    # Attach parts into message container.
    msg.attach(attachment)
    msg.attach(part1)
    msg.attach(part2)

    # Encode the payload using Base64.
    raw = encoders.encode_base64(msg)
    return raw


def main():
    to = "youremail@gmail.com"
    sender = "myemail@gmail.com"
    subject = "subject test1"
    msgHtml = r'Hi<br/>Html <b>hello</b>'
    msgPlain = "Hi\nPlain Email"
    message_text= "this is message text"
    SendMessage(sender, to, subject, msgHtml, msgPlain)


if __name__ == '__main__':
    main()

This function succeed in this code to send email without attachment:

def create_message_without_attachment (sender, to, subject, msgHtml, msgPlain):
    msg = MIMEMultipart('alternative')
    msg['Subject'] = subject
    msg['From'] = sender
    msg['To'] = to
    msg.attach(MIMEText(msgPlain, 'plain'))
    msg.attach(MIMEText(msgHtml, 'html'))

    raw = base64.urlsafe_b64encode(msg.as_bytes())
    raw = raw.decode()
    body = {'raw': raw}
    return body

Edit1 (to avoid duplicating the same answer): you will find in this answer, the code (and explanation) needed to send an email with (or without) an attachment.

Edit2: code improved thanks to randomfigure

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM