How to send an email through gmail without enabling 'insecure access'?
This was painful, but I seem to have something going now...
Python3 is not supported (yet)
I don't think it will be too hard to attain, as I was stumbling through converting packages without hitting anything massive: just the usual 2to3 stuff. Yet after a couple of hours I got tired of swimming upstream. At time of writing, I couldn't find a published package for public consumption for Python 3. The python 2 experience was straight-forward (in comparison).
Navigating the Google website is half the battle
No doubt, over time, this will change. Ultimately you need to download a client_secret.json
file. You can only (probably) do this setting up stuff via a web browser:
- You need a google account - either google apps or gmail. So, if you haven't got one, go get one.
- Get yourself to the developers console
- Create a new project, and wait 4 or 400 seconds for that to complete.
- Navigate to
API's and Auth
->Credentials
- Under
OAuth
selectCreate New Client ID
- Choose
Installed Application
as the application type and Other - You should now have a button
Download JSON
. Do that. It's yourclient_secret.json
—the passwords so to speak
But wait that's not all!
You have to give your application a "Product Name" to avoid some odd errors. (see how much I suffered to give you this ;-)
- Navigate to
API's & auth
->Consent Screen
- Choose your email
- Enter a PRODUCT NAME. It doesn't matter what it is. "Foobar" will do fine.
- Save
Newsflash! Whoa. Now there's even more!
- Navigate to API's & auth -> APIs -> Gmail API
- Click the button Enable API
Yay. Now we can update the emailing script.
Python 2
You need to run the script interactively the first time. It will open a web browser on your machine and you'll grant permissions (hit a button). This exercise will save a file to your computer gmail.storage
which contains a reusable token.
[I had no luck transferring the token to a machine which has no graphical browser functionality—returns an HTTPError. I tried to get through it via the lynx graphical browser. That also failed because google have set the final "accept" button to "disabled"!? I'll raise another question to jump this hurdle (more grumbling)]
First you need some libraries:
pip install --upgrade google-api-python-client
pip install --upgrade python-gflags
- you need to change the to and from addresses
- make sure you have the client_token.json file whereever the
Storage
instructions expect it - the directory needs to be writable so it can save the
gmail.storage
file
Finally some code:
import base64
import httplib2
from email.mime.text import MIMEText
from apiclient.discovery import build
from oauth2client.client import flow_from_clientsecrets
from oauth2client.file import Storage
from oauth2client.tools import run
# Path to the client_secret.json file downloaded from the Developer Console
CLIENT_SECRET_FILE = 'client_secret.json'
# Check https://developers.google.com/gmail/api/auth/scopes for all available scopes
OAUTH_SCOPE = 'https://www.googleapis.com/auth/gmail.compose'
# Location of the credentials storage file
STORAGE = Storage('gmail.storage')
# Start the OAuth flow to retrieve credentials
flow = flow_from_clientsecrets(CLIENT_SECRET_FILE, scope=OAUTH_SCOPE)
http = httplib2.Http()
# Try to retrieve credentials from storage or run the flow to generate them
credentials = STORAGE.get()
if credentials is None or credentials.invalid:
credentials = run(flow, STORAGE, http=http)
# Authorize the httplib2.Http object with our credentials
http = credentials.authorize(http)
# Build the Gmail service from discovery
gmail_service = build('gmail', 'v1', http=http)
# create a message to send
message = MIMEText("Message goes here.")
message['to'] = "[email protected]"
message['from'] = "[email protected]"
message['subject'] = "your subject goes here"
body = {'raw': base64.b64encode(message.as_string())}
# send it
try:
message = (gmail_service.users().messages().send(userId="me", body=body).execute())
print('Message Id: %s' % message['id'])
print(message)
except Exception as error:
print('An error occurred: %s' % error)
Hopefully that gets us all started. Not as simple as the old way, but does look a lot less complicated now I can see it in the flesh.
It seems that John Mee's answer is out of date. It does not work in July, 2016. Maybe due to the update of Gmail's API. I update his code (python 2) as below:
"""Send an email message from the user's account.
"""
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
import os
#from __future__ import print_function
import httplib2
import os
from apiclient import discovery
import oauth2client
from oauth2client import client
from oauth2client import tools
from apiclient import errors
SCOPES = 'https://www.googleapis.com/auth/gmail.compose'
CLIENT_SECRET_FILE = 'client_secret.json'
APPLICATION_NAME = 'Gmail API Python Quickstart'
try:
import argparse
flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args()
except ImportError:
flags = None
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
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 base64url encoded email object.
"""
message = MIMEText(message_text)
message['to'] = to
message['from'] = sender
message['subject'] = subject
return {'raw': base64.urlsafe_b64encode(message.as_string())}
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,
'sendEmail.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
if __name__ == "__main__":
try:
credentials = get_credentials()
http = credentials.authorize(httplib2.Http())
service = discovery.build('gmail', 'v1', http=http)
SendMessage(service, "me", CreateMessage("[email protected]", "[email protected]", "Test gmail automation", "Hello world"))
except Exception, e:
print e
raise
Note that if you encounter the error Insufficient Permission
, one possible reason is that the scope in program is not set correctly.
The other possible reason may be that you need to delete the storage json file ("sendEmail.json" in this program) and refresh your program. More details may be seen in this post.