Gmail & Outlook Thread Creator

This is a demo of email thread generator based from Gmail to Gmail and Outlook. It can

  • Send N email with different titles.
  • Create an email thread with N conversations (i.e. grouping N emails together).

Prerequisite:

  • Check out Gmail API quickstart to authorize API usage and save the private key file client_secret.json in your working directory.

Syntax:

#create 5 emails all with different title
python sender.py --to youralias@gmail.com --num_threads 5 
#create 10 emails in the same thread
python sender.py --to youralias@gmail.com --num_conversations 10 

Since Outlook classifies emails a little different, to make the conversational thread work, a user needs to copy to message ID from the first email. Command line message will provide a step by step guide.

# sender.py
import httplib2
import os
import oauth2client
from oauth2client import client, tools
import base64
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from apiclient import errors, discovery

SCOPES = 'https://www.googleapis.com/auth/gmail.send'
CLIENT_SECRET_FILE = 'client_secret.json'
APPLICATION_NAME = 'Gmail API Python Send Email'

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-email-send.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
        credentials = tools.run_flow(flow, store)
        print('Storing credentials to ' + credential_path)
    return credentials

def SendMessage(sender, to, subject, msgHtml, msgPlain, msgId='', threadId=''):
    credentials = get_credentials()
    http = credentials.authorize(httplib2.Http())
    service = discovery.build('gmail', 'v1', http=http)
    message1 = CreateMessage(sender, to, subject, msgHtml, msgPlain, msgId, threadId)
    return SendMessageInternal(service, "me", message1)

def SendMessageInternal(service, user_id, message):
    ''' if success, return threadID, else return None'''
    try:
        message = (service.users().messages().send(userId=user_id, body=message).execute())
        print('Thread Id: %s' % message['id'])
        return message['id'] 
    except errors.HttpError as error:
        print('An error occurred: %s' % error)
        return None

def CreateMessage(sender, to, subject, msgHtml, msgPlain, msgId, threadId):
    '''threadId: for gmail; msgId: for Outlook'''
    msg = MIMEMultipart()
    msg['Subject'] = subject
    msg['From'] = sender
    msg['To'] = to
    if msgId:
        msg['In-Reply-To'] = msgId
        msg['References'] = msgId
    msg.attach(MIMEText(msgPlain, 'plain'))
    msg.attach(MIMEText(msgHtml, 'html'))
    raw = base64.urlsafe_b64encode(msg.as_bytes())
    raw = raw.decode()
    body = {'raw': raw}
    if threadId:
        body['threadId'] = threadId  
    return body

def CreateConversations(sender, to, subject, msgHtml, msgPlain, msgId, threadId, num):
    for i in range(num):
        # retry until success
        success = False
        while not success:
            success = SendMessage(sender, to, subject, msgHtml, msgPlain, msgId, threadId)

if __name__ == '__main__':
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument('--to', type=str, default='foobarplay@gmail.com')
    parser.add_argument('--title', type=str, help='email title', default='Thread')
    parser.add_argument('--num_threads', type=int, help='num_threads', default=1)
    parser.add_argument('--num_conversations', type=int, help='num conversations of a thread (thread ID required)', default=1)
    args = parser.parse_args()                

    # create first mail, get msg id
    to = args.to
    sender = "youralias@gmail.com"
    msgHtml, msgPlain= "bar", "foo"
    is_gmail = 'gmail' in args.to

    for i in range(args.num_threads):
        subject = args.title + (str(i) if i>0 else '')
        # retry until success
        Id = None
        while not Id:
            Id = SendMessage(sender, to, subject, msgHtml, msgPlain)

    threadId, msgId = '', ''
    if args.num_conversations > 1:
        if is_gmail:
            threadId = Id
        else:
            msgId = input('Please check the latest mail and insert its msgId (e.g. <CAFi=xyz@mail.gmail.com>):')
        CreateConversations(sender, to, subject, msgHtml, msgPlain, msgId, threadId, args.num_conversations-1)
        print('Conversations created to the given thread')