Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Programmatically posting Gmail draft with both HTML and plaintext bodies specified by user

I am automating the creation of Gmail drafts in Python using the Gmail API. I am required to create HTML-formatted emails, but I also personally require creating a plaintext fallback, because it's the Right Thing To Do.

I thought I had all of the above working, until I tried making the plaintext fallback a little different from the HTML. It seems that Google takes it upon itself to create the plaintext fallback for me, rather than using the one I provide, so if my html body is <HTML><BODY>HTML Body</BODY></HTML> and my plaintext body is Plaintext body, the final plaintext body will be HTML Body, discarding the plaintext I provided.

My question: Does anyone know a way to get the Gmail API to use the plaintext I provide, rather than auto-generating the fallback for me?

One relevant item I've noticed: if I attach the HTML and Plaintext bodies in a different order, the reverse happens - GMail will automatically generate an HTML body based on my plaintext. So it seems like it's only paying attention to the last attached body.

Stripped down version of the code I'm using:

import base64
import os
import httplib2
import oauth2client
from oauth2client import client
from oauth2client import tools
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from pathlib import Path
from apiclient import errors
from apiclient import discovery

SCOPES = 'https://mail.google.com/'
CLIENT_SECRET_FILE = 'client_id.json'
APPLICATION_NAME = 'Test Client'

def get_credentials():
    home_dir = os.path.expanduser('~')
    credential_dir = os.path.join(home_dir, '.credentials')
    if not os.path.exists(credential_dir):
        os.makedirs(credential_dir)
    credential_path = os.path.join(credential_dir, 'gmail-python-quickstart.json')

    store = oauth2client.file.Storage(credential_path)
    credentials = store.get()
    if not credentials or credentials.invalid:
        flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES)
        flow.user_agent = APPLICATION_NAME
        if flags:
            credentials = tools.run_flow(flow, store, flags)
        else:  # Needed only for compatibility with Python 2.6
            credentials = tools.run(flow, store)
        print('Storing credentials to ' + credential_path)
    return credentials


def CreateDraft(service, user_id, message_body):
    message = {'message': message_body}
    draft = service.users().drafts().create(userId=user_id, body=message).execute()
    return draft


def CreateTestMessage(sender, to, subject):
    msg = MIMEMultipart('alternative')
    msg['Subject'] = subject
    msg['From'] = sender
    msg['To'] = to
    plain_text = "Text email message. It's plain. It's text."
    html_text = """\
<html>
  <head></head>
  <body>
    <p>HTML email message</p>
    <ol>
        <li>As easy as one</li>
        <li>two</li>
        <li>three!</li>
    </ol>
    <p>Includes <a href="http://stackoverflow.com/">linktacular</a> goodness</p>
  </body>
</html>
"""

    # Swapping the following two lines results in Gmail generating HTML
    # based on plaintext, as opposed to generating plaintext based on HTML
    msg.attach(MIMEText(plain_text, 'plain')) 
    msg.attach(MIMEText(html_text, 'html'))

    print('-----\nHere is the message:\n\n{m}'.format(m=msg))
    encoded = base64.urlsafe_b64encode(msg.as_string().encode('UTF-8')).decode('UTF-8') 
    return {'raw': encoded}


def main():
    credentials = get_credentials()
    http = credentials.authorize(httplib2.Http())
    service = discovery.build('gmail', 'v1', http=http)

    my_address = '[email protected]' # Obscured to protect the culpable

    test_message = CreateTestMessage(sender=my_address,
                                 to='[email protected]',
                                 subject='Subject line Here')

    draft = CreateDraft(service, my_address, test_message)


if __name__ == '__main__':
    main()

Update: Here are example gists of what I'm sending to Gmail vs what gets sent from Gmail, in both the HTML-then-plaintext and the plaintext-then-HTML orders (which generate different results)

like image 509
Bryant Avatar asked Oct 18 '22 16:10

Bryant


1 Answers

TL;DR: No.

Draft objects are shared with the web UI and mobile UIs, if the text/plain were not just a simple transformation of the text/html, then as soon as user used any other UI to edit the message that special customization would be lost. The reason to use the drafts UI is to allow user to share those drafts between other interfaces.

If you don't care about/want that ability don't use drafts and just send() it at the end, which like SMTP-MSA allows more flexibility.

like image 107
Eric D Avatar answered Oct 21 '22 05:10

Eric D