How do I send HTML Formatted emails, through the gmail-api for python

The accepted answer works, but in my journey down this rabbit hole, I found the right place to put the payload from the original question, so here's a full example. I'm using a service account with domain-wide delegation:

import base64 
from googleapiclient.discovery import build
from google.oauth2 import service_account
from email.message import EmailMessage

SCOPES = ['https://www.googleapis.com/auth/gmail.send']
CREDS = service_account.Credentials.from_service_account_file(
        serviceAcct, scopes=SCOPES, subject=senderEmail)

with build('gmail', 'v1', credentials=CREDS) as service:
    msg = EmailMessage()
    content="Message body in <b>html</b> format!"

    msg['To'] = recipientEmail
    msg['From'] = senderEmail
    msg['Subject'] = 'Gmail API test'
    # Use this for plain text
    # msg.set_content(content)
    # Otherwise, use this for html
    msg.add_header('Content-Type','text/html')
    msg.set_payload(content)

    encodedMsg = base64.urlsafe_b64encode(msg.as_bytes()).decode()

    try:
        sendMsg = service.users().messages().send(
            userId=senderEmail,
            body={ 'raw': encodedMsg }
        ).execute()
        print('Msg id:', sendMsg['id'])
    except Exception as e:
        print('Error:', e)

Try this:

    def CreateMessage(emailSubject, emailTo, emailFrom, message_body, emailCc, html_content=None):
        try:
            message = MIMEMultipart('alternative')
            message['to'] = emailTo
            message['from'] = emailFrom
            message['subject'] = emailSubject
            message['Cc'] = emailCc
            body_mime = MIMEText(message_body, 'plain')
            message.attach(body_mime)
            if html_content:
                html_mime = MIMEText(html_content, 'html')
                message.attach(html_mime)
            return {
                'raw': base64.urlsafe_b64encode(
                    bytes(
                        message.as_string(),
                        "utf-8")).decode("utf-8")}
        except Exception as e:
            print('Error in CreateMessage()', e)
            return '400'

After doing a lot of digging around, I started looking in to the python side of the message handling, and noticed that a python object is actually constructing the message to be sent for base64 encoding into the gmail-api message object constructor.

See line 63 from above: message = MIMEText(message_text)

The one trick that finally worked for me, after all the attempts to modify the header values and payload dict (which is a member of the message object), was to set (line 63):

  • message = MIMEText(message_text, 'html') <-- add the 'html' as the second parameter of the MIMEText object constructor

The default code supplied by Google for their gmail API only tells you how to send plain text emails, but they hide how they're doing that. ala... message = MIMEText(message_text)

I had to look up the python class email.mime.text.MIMEText object. That's where you'll see this definition of the constructor for the MIMEText object:

  • class email.mime.text.MIMEText(_text[, _subtype[, _charset]]) We want to explicitly pass it a value to the _subtype. In this case, we want to pass: 'html' as the _subtype.

Now, you won't have anymore unexpected word wrapping applied to your messages by Google, or the Python mime.text.MIMEText object


The Fixed Code

def create_message(sender, to, cc, subject, message_text):
    """Create a message for an email.

    Args:
    sender: Email address of the sender.
    to: Email address of the receiver.
    subject: The subject of the email message.
    message_text: The text of the email message.

    Returns:
    An object containing a base64url encoded email object.
    """
    print(sender + ', ' + to + ', ' + subject + ', ' + message_text)
    message = MIMEText(message_text,'html')
    message['to'] = to
    message['from'] = sender
    message['subject'] = subject
    message['cc'] = cc
    pprint(message)
    return {'raw': base64.urlsafe_b64encode(message.as_string())}