Gmail API Python: Optimize code to send email faster - python

The code below works. I can send emails through the Gmail API, but it takes awhile to send 10 personalized emails, based on wall time (20-30 secs). Is there a way to optimize the code below to send emails faster? There are quota limitations.
Is the max number emails one can send, a 100 per day? There seems to be a difference between number of emails one can send and the number of receipts per email. This is the documentation I am sourcing: https://developers.google.com/apps-script/guides/services/quotas
I am using the consumer version.
Feature Consumer (gmail.com)
Calendar events created 5,000
Contacts created 1,000
Documents created 250
Email recipients per day 100*
Code:
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
import mimetypes
import pandas as pd
import textwrap
SCOPES = 'https://www.googleapis.com/auth/gmail.send'
CLIENT_SECRET_FILE = 'secret.json'
APPLICATION_NAME = 'AppName'
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-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,message_text):
credentials = get_credentials()
http = credentials.authorize(httplib2.Http())
service = discovery.build('gmail', 'v1', http=http)
message1 = CreateMessageHtml(sender, to, subject, message_text)
result = SendMessageInternal(service, "me", message1)
return result
def CreateMessageHtml(sender, to, subject, message_text):
msg = MIMEText(message_text)
msg['Subject'] = subject
msg['From'] = sender
msg['To'] = to
return {'raw': base64.urlsafe_b64encode(msg.as_string())}
def SendMessageInternal(service, user_id, message):
try:
message = (service.users().messages().send(userId=user_id, body=message).execute())
print 'Message Id: %s' % message['id']
return message
except errors.HttpError, error:
print 'An error occurred: %s' % error
return "Error"
return "OK"
def main():
df = pd.read_csv('testdata.csv')
for index,row in df.iterrows():
to = row['Email']
sender = "sender"
subject = "subject"
dedent_text = '''Hello {}, \n
thank you for your question.'''.format(row['First'])
message_text = textwrap.dedent(dedent_text).strip()
SendMessage(sender, to, subject, message_text)
if __name__ == '__main__':
main()

Try caching the result of the service call, so that service is passed to SendMessage. This way, you don't take API call times to setup the API for each individual emails you send.
So at top of your main:
def main():
# Do once
credentials = get_credentials()
http = credentials.authorize(httplib2.Http())
service = discovery.build('gmail', 'v1', http=http)
df = pd.read_csv('testdata.csv')
for index,row in df.iterrows():
to = row['Email']
sender = "sender"
subject = "subject"
dedent_text = '''Hello {}, \n
thank you for your question.'''.format(row['First'])
message_text = textwrap.dedent(dedent_text).strip()
# service is is reused here for each message
SendMessage(service, sender, to, subject, message_text)
Also, if you need to send many messages, make sure you invoke one Python invokation per large batch, since starting up the interpreter and loading many packages can take a while each time.

Related

Show first and last name in header when using Gmail API (python)

I have my mail my_name#company.com and uses gmails API (and python) to send some mails. The problem is that when the mail hit the inbox the "from" is shown as my_name#company.com '<my_name#company.com>' where I want it to be First Name <my_name#company.com>.
I have tried using different variations of "First Name '<my_name#company.com>'" but I get a RefreshError: ('invalid_request: Invalid impersonation "sub" field.', '{\n "error": "invalid_request",\n "error_description": "Invalid impersonation \\u0026quot;sub\\u0026quot; field."\n}').
from __future__ import print_function
from googleapiclient.discovery import build
from apiclient import errors
from httplib2 import Http
from email.mime.text import MIMEText
import base64
from google.oauth2 import service_account
# Email variables. Modify this!
EMAIL_FROM = "First Last '<my_name#company.com>'"
EMAIL_TO = 'some_mail#hotmail.com'
EMAIL_SUBJECT = 'Hello from Me!'
EMAIL_CONTENT = 'Some body'
# Call the Gmail API
def service_account_login():
SCOPES = ['https://www.googleapis.com/auth/gmail.send']
SERVICE_ACCOUNT_FILE = 'my-credentials.json'
credentials = service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES)
delegated_credentials = credentials.with_subject(EMAIL_FROM)
service = build('gmail', 'v1', credentials=delegated_credentials)
return service
def create_message(sender, to, subject, message_text):
message = MIMEText(message_text)
message['to'] = to
message['from'] = sender
message['subject'] = subject
raw = base64.urlsafe_b64encode(message.as_bytes())
raw = raw.decode()
return {"raw": raw}
def send_message(service, user_id, message):
try:
message = (service.users().messages().send(userId=user_id, body=message)
.execute())
print('Message Id: %s' % message['id'])
return message
except errors.HttpError as error:
print('An error occurred: %s' % error)
service = service_account_login()
message = create_message(EMAIL_FROM, EMAIL_TO, EMAIL_SUBJECT, EMAIL_CONTENT)
sent = send_message(service,'me', message)
I just sent an email recently showing the name as you wanted, I got it with this line, similar to what you already had but without the single quotation marks:
message['from'] = "First Name <something#example.com>"
So the trick is fairly simple - the EMAIL_FROM is first used to create a service with that email, which is why you cannot write My Name <my_name#company.com>. You can do that when you need to specify the "FROM " mail e.g in the very buttom of create_message= f"First name<{EMAIL_FROM}>,...)

Sending an email using the gmail api

I am writing a simple script to send an email using the google api:
My code looks like:
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
import mimetypes
from email.mime.image import MIMEImage
from email.mime.audio import MIMEAudio
from email.mime.base import MIMEBase
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, attachmentFile=None):
credentials = get_credentials()
http = credentials.authorize(httplib2.Http())
service = discovery.build('gmail', 'v1', http=http)
if attachmentFile:
message1 = createMessageWithAttachment(sender, to, subject, msgHtml, msgPlain, attachmentFile)
else:
message1 = CreateMessageHtml(sender, to, subject, msgHtml, msgPlain)
result = SendMessageInternal(service, "me", message1)
return result
def SendMessageInternal(service, user_id, message):
try:
message = (service.users().messages().send(userId=user_id, body=message).execute())
print ('Message Id: %s' % message['id'])
return message
except errors.HttpError as error:
print ('An error occurred: %s' % error)
return "Error"
return "OK"
def CreateMessageHtml(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'))
return {'raw': base64.urlsafe_b64encode(msg.as_string())}
def main():
to = "user#gmail.com"
sender = "user#gmail.com"
subject = "testing"
msgHtml = "Hi<br/>Html Email"
msgPlain = "Hi\nPlain Email"
SendMessage(sender, to, subject, msgHtml, msgPlain)
if __name__ == '__main__':
main()
However, when I run this script, I get the error that:
I went to go ensure the files existed, and navigated to the path. The directory contained these files:
so clearly the clientsecrets.py file does exist. I am unsure of how to resolve this error, as the file is clearly there and the terminal does map the file correctly. Any suggestions would be appreciated. Thank you!
In your error message We can see No such file or directory: 'client_secret.json'. Do you have it then store it in your current directory If you don't have it, you can see how to retrieve client_secret.json using Python Quickstart
Don't forget to turn on your gmail API

Error 400 when sending a message using python

I am trying to send an email using Gmail API. I have successfully authenticated and have a client_secret.json file on my machine.
I have been able to get a list of labels using the quickstart example on the Gmail API website
I have reset my scope successfully to
SCOPES = 'https://mail.google.com'
allowing full access to my gmail account.
I have a python script, compiled from here and here. See below. When executing the script I get the following error message:
An error occurred: https://www.googleapis.com/gmail/v1/users/me/messages/send?alt=json returned "'raw' RFC822 payload message string or uploading message via /upload/* URL required">
Any thoughts on what I am doing wrong and how to fix it?
from __future__ import print_function
import argparse
import time
from time import strftime, localtime
import os
import base64
import os
import httplib2
from httplib2 import Http
from apiclient import errors
from apiclient import discovery
import oauth2client
from oauth2client import file, client, tools
SCOPES = 'https://mail.google.com'
CLIENT_SECRET_FILE = 'client_secret.json'
store = file.Storage('storage.json')
credentials = store.get()
if not credentials or credentials.invalid:
flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args()
flow = client.flow_from_clientsecrets('client_secret.json', SCOPES)
credentials = tools.run_flow(flow, store, flags)
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 = 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 send_message(service, user_id, message):
"""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.
"""
try:
message = (service.users().messages().send(userId=user_id, body=message)
.execute())
print ('Message Id: %s' % message['id'])
return message
except errors.HttpError, error:
print ('An error occurred: %s' % error)
def main():
credentials = get_credentials()
http = credentials.authorize(httplib2.Http())
service = discovery.build('gmail', 'v1', http=http)
send_message(service, 'me','test message')
main()
A message has to be created like it is outlined in the Sending Email guide:
def create_message(sender, to, subject, message_text):
message = MIMEText(message_text)
message['to'] = to
message['from'] = sender
message['subject'] = subject
return {'raw': base64.urlsafe_b64encode(message.as_string())}
def main():
credentials = get_credentials()
http = credentials.authorize(httplib2.Http())
service = discovery.build('gmail', 'v1', http=http)
message = create_message(
'sender#gmail.com', 'receiver#gmail.com', 'Subject', 'Message text'
)
send_message(service, 'me', message)

Python Gmail Api: Email is not sending as the specified from

import httplib2
import os
from httplib2 import Http
from apiclient import discovery
import oauth2client
from oauth2client import client
from oauth2client import tools
import base64
from email.mime.audio import MIMEAudio
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import mimetypes
from apiclient import errors
try:
import argparse
flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args()
except ImportError:
flags = None
#SCOPES = 'https://www.googleapis.com/'
SCOPES = 'https://www.googleapis.com/auth/gmail.compose'
CLIENT_SECRET_FILE = 'client_secret.json'
APPLICATION_NAME = 'Gmail API Quickstart'
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-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 compatability with Python 2.6
credentials = tools.run(flow, store)
print 'Storing credentials to ' + credential_path
return credentials
def CreateMessage(sender, to, 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 base64 encoded email object.
"""
message = MIMEText(message_text)
message['to'] = to
message['from'] = sender
message['subject'] = subject
return {'raw': base64.b64encode(message.as_string())}
def SendMessage(service, user_id, message):
"""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.
"""
try:
message = (service.users().messages().send(userId=user_id, body=message)
.execute())
print 'Message Id: %s' % message['id']
return message
except errors.HttpError, error:
print 'An error occurred: %s' % error
credentials = get_credentials()
http = credentials.authorize(httplib2.Http())
service = discovery.build('gmail', 'v1', http=http)
testMessage = CreateMessage('hey#gmail.com', 'johndoeisawesome#gmail.com', 'ENTER SUBJECT', 'ENTER EMAIL BODY')
testSend = SendMessage(service, 'me', testMessage)
i get email in johndoeisawesome#gmail.com as the email account that i enabled gmail api as, NOT hey#gmail.com
is that the correct behavior?
Then, whats the point of filling out 'from' in CreateMessage()?
You can use a Gmail alias in the From-header,
From: myalias#gmail.com
You can follow this guide to set up an alias.

New Python Gmail API - Only Retrieve Messages from Yesterday

I've been updating some scripts to the new Python Gmail API. However, I am confused as how to update the following so that I only retrieve messages from yesterday. Can anyone show me how to do this?
The only way I can currently see is to loop through all messages and only parse those with epochs in the correct time range. However, that seems horribly inefficient if I have 1000's of messages. There must be a more efficient way to do this.
from apiclient import discovery
import oauth2client
from oauth2client import client
from oauth2client import tools
import os
import httplib2
import email
from apiclient.http import BatchHttpRequest
import base64
from bs4 import BeautifulSoup
import re
import datetime
try:
import argparse
flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args()
except ImportError:
flags = None
SCOPES = 'https://www.googleapis.com/auth/gmail.readonly'
CLIENT_SECRET_FILE = '/Users/sokser/Downloads/client_secret.json'
APPLICATION_NAME = 'Gmail API Python Quickstart'
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 = 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 visible(element):
if element.parent.name in ['style', 'script', '[document]', 'head', 'title']:
return False
elif re.match('<!--.*-->', str(element)):
return False
return True
def main():
"""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)
#Get yesterdays date and the epoch time
yesterday = datetime.date.today() - datetime.timedelta(1)
unix_time= int(yesterday.strftime("%s"))
messages = []
message = service.users().messages().list(userId='me').execute()
for m in message['messages']:
#service.users().messages().get(userId='me',id=m['id'],format='full')
message = service.users().messages().get(userId='me',id=m['id'],format='raw').execute()
epoch = int(message['internalDate'])/1000
msg_str = str(base64.urlsafe_b64decode(message['raw'].encode('ASCII')),'utf-8')
mime_msg = email.message_from_string(msg_str)
#print(message['payload']['parts'][0]['parts'])
#print()
mytext = None
for part in mime_msg.walk():
mime_msg.get_payload()
#print(part)
#print()
if part.get_content_type() == 'text/plain':
soup = BeautifulSoup(part.get_payload(decode=True))
texts = soup.findAll(text=True)
visible_texts = filter(visible,texts)
mytext = ". ".join(visible_texts)
if part.get_content_type() == 'text/html' and not mytext:
mytext = part.get_payload(decode=True)
print(mytext)
print()
if __name__ == '__main__':
main()
You can pass queries to the messages.list method that searches for messages within a date range. You can actually use any query supported by Gmail's advanced search.
You do this, which will just return messages.
message = service.users().messages().list(userId='me').execute()
But can do this to search for messages sent yesterday, by passing the q keyword argument, and a query specifying the before: and after: keywords.
from datetime import date, timedelta
today = date.today()
yesterday = today - timedelta(1)
# do your setup...
user_id = 'user email address'
# Dates have to formatted in YYYY/MM/DD format for gmail
query = "before: {0} after: {1}".format(today.strftime('%Y/%m/%d'),
yesterday.strftime('%Y/%m/%d'))
response = service.users().messages().list(userId=user_id,
q=query).execute()
# Process the response for messages...
You can also try this against their GMail messages.list reference page.

Categories

Resources