Send mail in Gmail with python - python

I'm trying to send a simple test email using the Gmail API but I keep getting the same error for every code sample I find.
My code at the moment is this:
def validationService():
SCOPES = ['https://mail.google.com/']
SERVICE_ACCOUNT_FILE = 'creds.json'
creds = service_account.Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=SCOPES)
service = build('gmail', 'v1', credentials=creds)
return service
def SendMessage(service, user_id, message):
try:
message = (service.users().messages().send(userId=user_id, body=message).execute())
print('Message Id:',message['id'])
return message
except errors.HttpError as error:
print('An error occurred:', error)
def CreateMessage(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().encode()).decode()}
service = validationService()
email = CreateMessage("sendermail#gmail.com", "receiverrmail#gmail.com", "Test", "This is a test")
sent = SendMessage(service, "sendermail#gmail.com", email)
Returns
>>> An error occurred: <HttpError 400 when requesting https://www.googleapis.com/gmail/v1/users/me/messages/send?alt=json returned "Bad Request">
I also don't understand the difference between the "sender" parameter at CreateMessage and userId at SendMessage.
If serves a purpose, I'm using a Service Account credential
-
Thanks!

When using a service account with the Gmail API. You need to set domain-wide delegation, which allows the service account to impersonate any user in your G Suite domain. Therefore, you must have a G Suite Account to be able to use domain wide-delegation as the docs say:
If you have a G Suite domain—if you use G Suite, for example—an
administrator of the G Suite domain can authorize an application to
access user data on behalf of users in the G Suite domain.
So now, why do you need to impersonate a user(a real person)? it's due to the fact a service account is a bot(not a real person) that is used to server-to-server interactions making possible your app calls Google APIs and although the service account has a parameter called client_email, which has a structure like name#project-randomnumber.iam.gserviceaccount.com it's not a real email that belongs to a real person (kind of confusing I know).
Having said that, I made some changes to your code. First, I modified your validationService function in order to build the service using domain-wide delegation.
def validationService():
# Set the crendentials
credentials = service_account.Credentials.\
from_service_account_file(SERVICE_ACCOUNT_FILE, scopes= SCOPES)
# Delegate the credentials to the user you want to impersonate
delegated_credentials = credentials.with_subject(USER_EMAIL)
service = discovery.build('gmail', 'v1', credentials=delegated_credentials)
return service
In your SendMessage function is not necessarily to pass the user who is going to send the email. Using me is enough as the Users.messages: send Parameters state:
userId string The user's email address. The special value me can be
used to indicate the authenticated user.
def SendMessage(service, message):
message = service.users().messages().send(userId="me", body=message).execute()
return message
Your whole code at the end could look like this one:
from googleapiclient import discovery, errors
from oauth2client import file, client, tools
from google.oauth2 import service_account
from email.mime.text import MIMEText
import base64
SERVICE_ACCOUNT_FILE = 'service_account.json'
SCOPES = [' https://mail.google.com/']
# The user we want to "impersonate"
USER_EMAIL = "user#domain"
def validationService():
# Set the crendentials
credentials = service_account.Credentials.\
from_service_account_file(SERVICE_ACCOUNT_FILE, scopes= SCOPES)
# Delegate the credentials to the user you want to impersonate
delegated_credentials = credentials.with_subject(USER_EMAIL)
service = discovery.build('gmail', 'v1', credentials=delegated_credentials)
return service
def SendMessage(service, message):
message = service.users().messages().send(userId="me", body=message).execute()
return message
def CreateMessage(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().encode()).decode()}
def main():
try:
service = validationService()
email = CreateMessage(USER_EMAIL, "receiverrmail#domain", "Test", "This is a test")
email_sent = SendMessage(service, email)
print('Message Id:', email_sent['id'])
except errors.HttpError as err:
print('\n---------------You have the following error-------------')
print(err)
print('---------------You have the following error-------------\n')
if __name__ == '__main__':
main()
Notice
You also need to allow your service account to access Google's API when using domain-wide delegation by setting the Managing API client access on your G Suite account.

You don't break your head... Just use email and smtplib libraries. Very simple and fun sending email through Python, if you ask me. I have given my coding below and you should develop your own code based on this. If it works, just let me know - I will be happy. here it is....
import email, smtplib, os, time, fnmatch
from datetime import date, timedelta
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
def sendMailTo(recepient_id, today):
login_id = 'myemailID#gmail.com'
login_pwd = "mypassword"
fro_ = login_id
s_name = 'smtp.gmail.com'
s_port = 587
fileList = []
fi_type = '*.pdf'
file_path = "C:\\Myfolder\\Subfolder"
srvr = smtplib.SMTP(s_name, s_port)
srvr.ehlo()
srvr.starttls()
srvr.ehlo()
srvr.login(login_id, login_pwd)
sub = "Your subject here"
# loading MIMEMultipart obj onto outer var
outer = MIMEMultipart('alternative')
outer["From"] = 'Your company/Personal Name'
outer["To"] = recepient_id
outer['Subject'] = sub
# storing only pdf files
fileList = fnmatch.filter(os.listdir(file_path), fi_type)
for fi in fileList:
fi_name = os.path.join(file_path, fi)
fp = open(fi_name, 'rb')
img = MIMEImage(fp.read(), _subtype='jpg')
fp.close()
img.add_header('Content-disposition', "attachment", filename = fi)
outer.attach(img)
#start sending email with attachment with original file name
srvr.sendmail(fro_, recepient_id, outer.as_string())
part = None
outer = None
srvr.quit()

Related

"unauthorized_client" error when using google API to send an email

I get this error when running my code:
google.auth.exceptions.RefreshError: ('unauthorized_client: Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested.', {'error': 'unauthorized_client', 'error_description': 'Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested.'})
Here is the code:
from __future__ import print_function
from googleapiclient import discovery, errors
from oauth2client import file, client, tools
from google.oauth2 import service_account
from email.mime.text import MIMEText
import base64
SERVICE_ACCOUNT_FILE = 'keys.json'
SCOPES = [' https://www.googleapis.com/auth/gmail.send']
# The user we want to "impersonate"
USER_EMAIL = "myName#myDomain.com"
def validationService():
# Set the crendentials
credentials = service_account.Credentials. \
from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=SCOPES)
# Delegate the credentials to the user you want to impersonate
delegated_credentials = credentials.with_subject(USER_EMAIL)
service = discovery.build('gmail', 'v1', credentials=delegated_credentials)
return service
def send_message(service):
gmail_from = 'myName#myDomain.com'
gmail_to = 'anotherName#gmail.com'
gmail_subject = 'Hello World'
gmail_content = 'test test'
message = MIMEText(gmail_content)
message['to'] = gmail_to
message['from'] = gmail_from
message['subject'] = gmail_subject
raw = base64.urlsafe_b64encode(message.as_bytes())
raw = raw.decode()
body = {'raw': raw}
try:
message = (service.users().messages().send(userId='me', body=body)
.execute())
print('your message has been sent')
return message
except errors.HttpError as error:
print('An error occurred: %s' % error)
send_message(validationService())
I don't understand where in the code my email address "gmail_from" is connected to my email address. Apart from that i've given access to my IDE in gmail:
I've also created in the google console OAuth 2.0 Client IDs credentials and Service Accounts credentials but i don't really understand how/where to use these.
What am I missing?
On the google website: https://developers.google.com/identity/protocols/oauth2/service-account#error-codes I have found that I needed to "In the Domain-wide delegation page in the Admin console, remove the client, and re-add it with the numeric ID." but i don't understand how to do that nor how that would help.
There is an extra space in the scope:
SCOPES = [' https://www.googleapis.com/auth/gmail.send']
^ here

Gmail API - set sender name when sending mail from a g-suite service account

I want to dynamically change the name that shows up in the recipients mailbox (appears on the left of the subject + content of the email).
No matter what I try, the name always ends up as 'info', and the 'from' address is always 'info#g-suite-domain'
Following is the code I'm using.
import argparse
import base64
import os
import sys
from email.mime.text import MIMEText
from apiclient import errors
from google.oauth2 import service_account
from googleapiclient.discovery import build
from httplib2 import Http
def create_message(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, 'html')
message['to'] = to
message['from'] = sender
message['subject'] = subject
raw = base64.urlsafe_b64encode(message.as_bytes())
raw = raw.decode()
body = {'raw': raw}
return body
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 as error:
print('An error occurred: %s' % error)
def service_account_login():
SCOPES = [
'https://mail.google.com',
'https://www.googleapis.com/auth/gmail.compose',
'https://www.googleapis.com/auth/gmail.modify',
'https://www.googleapis.com/auth/gmail.readonly',
]
dirname = os.path.dirname(os.path.realpath(__file__))
SERVICE_ACCOUNT_FILE = dirname + '/service-key.json'
credentials = service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES)
delegated_credentials = credentials.with_subject(
'info#g-suite-domain')
service = build('gmail', 'v1', credentials=delegated_credentials)
return service
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--to', required=True)
parser.add_argument('--subject', required=True)
args = parser.parse_args()
content = sys.stdin.read()
EMAIL_FROM = "service-acount-id#project-id.iam.gserviceaccount.com"
service = service_account_login()
# Call the Gmail API
message = create_message(EMAIL_FROM, args.to, args.subject, content)
sent = send_message(service, 'me', message)
I have tried...
relevant solutions I could find online but none helped.
setting both the EMAIL_FROM and with_subject address to the form of "Name <sender-address#domain>" which has no effect.
changing the 'Send mail as' option in gmail for 'info#g-suite-domain'
specifying the name for both email accounts in gmail, google admin, and google console.
creating a new project + service account
reading the googleapiclient.discovery library for problematic code but could not find anything.
I previously transferred the domain providers from Wix to Namecheap, and I didn't realize the MX records were not transferred. It seems, due to the lack of MX record, the from address would always rewrite to 'info#g-suite-domain' (ignoring the message['from'] value).
I have obtained the desired result with:
message["from"] = "info#g-suite-domain <info#g-suite-domain>"

Is it possibile that emails are blocked by gmail while sending several emails through cron job?

I have a question concerning the behavior of my code. The code below does the following:
- get credentials for gmail API
- tries to send a message
- in the mean time, a cron job is working
I noticed that they work fine together. Now, the first message is sent and received well. But then the second and all other mails are sent but never reach the receipient. First email is received in the inbox. I checked the Spam folder but there's nothing there. I checked the Sent folder of the sender and the mails are all sent.
I am new to both elements and I can't figure out what's going on.
Here's my code:
from __future__ import print_function
import httplib2
import os
import atexit
import time
from apiclient import discovery
from oauth2client import client
from oauth2client import tools
from oauth2client.file import Storage
from flask import Flask
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.interval import IntervalTrigger
import base64
from email.mime.text import MIMEText
import mimetypes
app = Flask(__name__)
try:
import argparse
flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args()
except ImportError:
flags = None
# If modifying these scopes, delete your previously saved credentials
# at ~/.credentials/gmail-python-quickstart.json
SCOPES = 'http://mail.google.com'
CLIENT_SECRET_FILE = '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.
"""
current_dir = os.getcwd()
credential_dir = os.path.join(current_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, 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 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:
print ('An error occurred')
#app.route("/home")
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)
message = create_message('foo#foo.com', 'foo#foo.com', 'Test', 'Ciao')
send_message(service, 'me', message)
print(time.strftime("%A, %d. %B %Y %I:%M:%S %p"))
scheduler = BackgroundScheduler()
scheduler.start()
scheduler.add_job(
func=main,
trigger=IntervalTrigger(seconds=20),
id='printing_job',
name='Print date and time every five seconds',
replace_existing=True)
# Shut down the scheduler when exiting the app
atexit.register(lambda: scheduler.shutdown())
if __name__ == '__main__':
app.run()
I would like to know if I am missing something.
I tried sending both receipient and sender email addresses but nothing has changed.

oauth2client.clientsecrets.InvalidClientSecretsError: Missing property "redirect_uris" in a client type of "web"

I am trying to send Gmail from a Python script to myself, and have the following code that used to be working:
#!/usr/bin/env python
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
import httplib2
from apiclient import discovery
import oauth2client
from oauth2client import client
from oauth2client import tools
from apiclient import errors
from config import MY_EMAIL, CLIENT_SECRET_FILE
SCOPES = 'https://www.googleapis.com/auth/gmail.compose'
APPLICATION_NAME = 'Gmail API Python Quickstart'
try:
import argparse
flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args()
except ImportError:
flags = None
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 as error:
print('An error occurred: %s' % error)
def create_message(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
def send_email(sender=MY_EMAIL, receiver=MY_EMAIL, subject=None, body=None):
print("sending email to: %s, from: %s" % (receiver, sender))
if not body:
raise Exception("You need a body to send an email")
credentials = get_credentials()
http = credentials.authorize(httplib2.Http())
service = discovery.build('gmail', 'v1', http=http)
send_message(service, "me", create_message(sender, receiver, subject, body))
if __name__ == "__main__":
send_email(subject="Test gmail automatic emails", body="Hello world")
My client_secret.json looks like
{"web":{"client_id":"23423423423-234234234.apps.googleusercontent.com","project_id":"prime-imagery-4444","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://accounts.google.com/o/oauth2/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"asdfasdfasdf-trWCLxFdb", "redirect_uris": ["http://localhost:8000/fakeurl"]}}
I added in redirect_uris as that was the only good advice I found online, but I don't want to redirect. Last time I ran this script, having the client_secret was enough to send Gmails from my Gmail account using Python.
Just go to your Credentials in your Google Dev Console and add what it's asking. http://localhost:8080/oauth2callback or https://www.samplesite.com/oauth2callback. Adding the 'oauth2callback' is the redirect URI. Check the Create authorization credentials.
I faced the same issue.
So after you add the redirect_uri also don't forget to redownload the clien_secret.json file.
You need to perform 3 steps. (step 2 wasn't specified in other answers):
1 ) You add a uri to Authorized redirect URIs.
2 ) The Authorized redirect URIs uri is equal to the one you entered in Authorized JavaScript origins just with a closing slash /.
3 ) Download client_secret.json again and add it to your working direcory.

Change the maximum line length limit using Gmail API

I am trying to send emails by Gmail API with Python. But when I check the received email,I find that it seems that each row is automatically broken at 78th chars. The screen shot can be seen as below:
It seems to be caused by RFC 2045. But can I reset some parameters to make it longer for each line?
My code can be seen as below:
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 = 'send survey about assistance tool for Stack Overflow'
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__":
credentials = get_credentials()
http = credentials.authorize(httplib2.Http())
service = discovery.build('gmail', 'v1', http=http)
SendMessage(service, "me", CreateMessage("sender#gmail", "receiver#gmail.com", "Testing sending emails", "I want all my one sentence to be in each line and it will breaked by my specified line breakers.\n But it seems the line will break at 78 chars and can I reset that parameters to make it longer?"))
Any suggestions will be welcomed and thanks in advance.

Categories