Как отправлять электронные письма в формате HTML, через gmail-api для python

Использование примера кода из API GMail Пример: отправка почты, и после соблюдения правил аутентификации достаточно просто отправить сгенерированное программным способом электронное письмо через учетную запись gmail. Из этого примера не очевидно, как настроить это письмо в формате HTML.


Вопрос

Как мне получить форматирование HTML в моих сообщениях отправки gmail-api, используя python?

У меня есть это...

message_body = "Hello!\nYou've just received a test message!\n\nSincerely,\n-Test Message Generator\n"

и я хочу, чтобы это было так...

Hello!
You've just received a test message!

Sincerely,
-Test Message Generator

Пример исходного кода из GMail-API

Ниже приведена немного измененная версия примера, но она все еще работает:

import argparse
import base64
from pprint import pformat
from pprint import pprint
import httplib2
import os
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText

from apiclient import discovery
from oauth2client import client
from oauth2client import tools
from oauth2client.file import Storage


SCOPES = 'https://mail.google.com/'
CLIENT_SECRET_FILE = 'client_secret.json'
APPLICATION_NAME = 'Test EMail App'


def get_credentials():
    """Gets valid user credentials from storage.

    If nothing has been stored, or if the stored credentials are invalid,
    the OAuth2 flow is completed to obtain the new credentials.

    Returns:
        Credentials, the obtained credential.
    """

    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 = 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 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)
    message['to'] = to
    message['from'] = sender
    message['subject'] = subject
    message['cc'] = cc
    pprint(message)
    return {'raw': base64.urlsafe_b64encode(message.as_string())}

def send_message(service, user_id, message_in):
    """Send an email message.

    Args:
    service: Authorized Gmail API service instance.
    user_id: User's email address. The special value "me"
    can be used to indicate the authenticated user.
    message: Message to be sent.

    Returns:
    Sent Message.
    """
    pprint(message_in)
    try:
        message = (service.users().messages().send(userId=user_id, body=message_in).execute())
        pprint(message)
        print ('Message Id: %s' % message['id'])
        return message
    except errors.HttpError, error:
        print ('An error occurred: %s' % error)

def main(cli):
    """Shows basic usage of the Gmail API.

    Creates a Gmail API service object and outputs a list of label names
    of the user's Gmail account.
    """


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

    email_msg = create_message(cli.addr_from, cli.addr_to, cli.addr_cc, cli.subject, cli.message)
    msg_out = service.users().messages().send(userId = 'me', body = email_msg).execute()
    pprint(msg_out)


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('-m', '--message',   help = 'The message to send in the email', default='<MESSAGE = unfinished>')
    parser.add_argument('-t', '--addr_to',   help = 'the list of comma separated emails to send', default='cbsd.tools@gmail.com')
    parser.add_argument('-s', '--subject',   help = 'the email subject', default='<SUBJECT = undefined>')
    parser.add_argument('-c', '--addr_cc',   help = 'email CC\'s', default='')
    parser.add_argument('-f', '--addr_from', help = 'Email address to send from', default='cbsd.tools@gmail.com')
    cli = parser.parse_args()
    pprint(dir(cli))

    main(cli)

Попробовав, как я мог, с этим кодом и его вариациями, я не смог получить код в формате html и не смог получить простые escape-символы для создания возврата каретки там, где они должны быть.


Вот что не сработало

Попытка следующего тоже не сработала:

  1. модифицирующий line 69 добавить дополнительные параметры словаря сообщений... т.е.
    • {'raw': base64.urlsafe_b64encode(message.as_string()), 'payload': {'mimeType': 'text/html'}}
    • Как документировано здесь, в документации по GMail API
  2. Добавление множества экранированных обратных косых черт в текст сообщения:
    • \n... т.е. \\n ни \\\n и просто отображается как эти точные символы
    • Добавление <br> </br> <br/> не добавлял новые строки и просто отображал как точные символы
    • Добавление \r не добавлял новые строки и просто отображал как точный символ

Причины, это не повторяющийся вопрос

4 ответа

Решение

После долгих поисков я начал изучать сторону обработки сообщений, связанную с python, и заметил, что объект python фактически создает сообщение, которое будет отправлено для кодирования base64, в конструктор объекта сообщения gmail-api.

Смотрите строку 63 сверху: message = MIMEText(message_text)

Один трюк, который, наконец, сработал для меня, после всех попыток изменить значения заголовка и dict полезной нагрузки (который является членом message объект), должен был установить (line 63):

  • message = MIMEText(message_text, 'html') <- добавить 'html' как второй параметр конструктора объекта MIMEText

Код по умолчанию, предоставленный Google для их API Gmail, говорит только о том, как отправлять простые текстовые электронные письма, но они скрывают, как они это делают. ала... message = MIMEText(message_text)

Я должен был посмотреть класс Python email.mime.text.MIMEText объект. Вот где вы увидите это определение конструктора для объекта MIMEText:

  • класс email.mime.text.MIMEText(_text[, _subtype[, _charset]]) Мы хотим явно передать ему значение в _subtype, В этом случае мы хотим передать: 'html' как _subtype,

Теперь у вас больше не будет неожиданного переноса слов к вашим сообщениям от Google или Python mime.text.MIMEText объект


Фиксированный код

def create_message(sender, to, cc, subject, message_text):
    """Создать сообщение для электронной почты.

    Args:
    отправитель: адрес электронной почты отправителя.
    to: адрес электронной почты получателя.
    subject: тема сообщения электронной почты.
    message_text: текст сообщения электронной почты.

    Возвращает:
    Объект, содержащий закодированный в base64url объект электронной почты.
    """
    print(отправитель + ', ' + to + ', ' + subject + ', ' + message_text)
    message = MIMEText(message_text,'html')
    сообщение ['to'] = к
    сообщение ['от'] = отправитель
    сообщение ['тема'] = тема
    message['cc'] = cc
    pprint(сообщение)
    return {'raw': base64.urlsafe_b64encode(message.as_string())}

Попробуй это:

    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'

Принятый ответ работает, но в моем путешествии по этой кроличьей норе я нашел правильное место для размещения полезной нагрузки из исходного вопроса, так что вот полный пример. Я использую учетную запись службы с делегированием на уровне домена:

      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)

Кроме того, в конце 2023 года это было в моем коде и до сих пор не вызывало никаких проблем:

                  # text
            mime_message.set_content(body)

            # html
            mime_message.add_alternative(
                body,
                subtype="html",
            )
Другие вопросы по тегам