1

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
JinSnow
  • 1,553
  • 4
  • 27
  • 49
  • 1
    Can you add the logs? You might want to follow the [solution](https://github.com/google/google-api-python-client/issues/93) in this reported ticket by adding `msg.set_payload(contents)` and `encoders.encode_base64(msg)`. Hope this helps – Mr.Rebot Mar 05 '17 at 12:02
  • @Mr.Rebot thanks a lot for the precious link! I updated the code using my interpretation about it (the guy did not post the whole code, so I did not really understood his "I also needed to add the second line as below" . The code returns `TypeError: Attach is not valid on a message with a non-multipart payload` I also don't understand if I should add also add the `raw =....` part from the function that is working (cf the create_message_without_attachment() at bottom my question) – JinSnow Mar 05 '17 at 15:37
  • You will find the answer here: http://stackoverflow.com/a/37267330/1486850 – JinSnow Apr 08 '17 at 19:57

1 Answers1

5

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

JinSnow
  • 1,553
  • 4
  • 27
  • 49
  • 1
    in your function `create_Message_with_attachement` doing `encoders.encode_base64(attachement)` is redundant since you're encoding the entire message later with `urlsafe_b64encode` – randomfigure Oct 18 '19 at 12:29