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.
Related
I have been trying to send a test email via the Gmail API configuration with little success. I am not sure what is wrong exactly, but the error I am receiving is as follows:
File "c:\Development\foodSaver\emailAPI.py", line 20, in send_message
message = (service.users().messages().send(userId=user_id, body=message).execute())
AttributeError: 'str' object has no attribute 'users'
During handling of the above exception, another exception occurred:
Traceback (most recent call last): File
"c:\Development\foodSaver\emailAPI.py", line 28, in
send_message('Gmail', 'Person','yes') File "c:\Development\foodSaver\emailAPI.py", line 23, in send_message
except errors.HttpError as error: NameError: name 'errors' is not defined
I have put the Credentials.json file in the same directory as my project, but was never asked to reference it in this script.
The code I have so far (that is producing this error):
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import base64
import os
# Writes gmail message
def create_message(sender, to, subject, message_text):
message = MIMEText(message_text)
message['to'] = to
message['from'] = sender
message['subject'] = subject
message = message.as_string()
message = message.encode('utf-8')
return {'raw': base64.urlsafe_b64encode(message)}
# Sends gmail message
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)
create_message('abittar#nyit.edu','abittar#nyit.edu','This is a test email.','This is the test body message.')
send_message('Gmail', 'Person','yes')
For the last part I tried inputting random things for the def variables in order to test.
The first and second errors occur when you pass the wrong arguments in your send_message function. The content of service should be resource object and the content of user_id should be an email or 'me'.
You need to create credentials and use it to construct a resource to interact with Gmail API. You can start by following this Python Quickstart for Gmail on how to setup your credentials and install necessary libraries. Once you're done with setup, your code should look like this:
Note: You can copy and paste the code and replace the sender and recipient email. Make sure that the content of credentials.json is on the same level as your main script. In my example below, I used this to obtain the content of credentials.json
from __future__ import print_function
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from googleapiclient.errors import HttpError
from email.mime.text import MIMEText
import base64
import os
# If modifying these scopes, delete the file token.json.
SCOPES = ['https://www.googleapis.com/auth/gmail.send']
# Writes gmail message
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().encode()).decode()}
# Sends gmail message
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 HttpError as error:
print('An error occurred: %s' % error)
def main():
"""Shows basic usage of the Gmail API.
Lists the user's Gmail labels.
"""
creds = None
# The file token.json stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.json'):
creds = Credentials.from_authorized_user_file('token.json', SCOPES)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('token.json', 'w') as token:
token.write(creds.to_json())
service = build('gmail', 'v1', credentials=creds)
msg = create_message('sender email', 'recipient email', 'This is a test email.',
'This is the test body message.')
send_message(service, 'me', msg)
if __name__ == '__main__':
main()
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
I'm inserting emails with the API into my mailbox. Attachments work and do show up in the email itself. Usually in the inbox/message list view, there is an attachment paperclip symbol at the right end to indicate the message has an attachment (https://i.imgur.com/XbYIE9b.png). But this doesn't seem to be working. My code to generate the email and insert it is below. Is this a limitation of the insert API or I'm not generating the email with attachment correctly?
from __future__ import print_function
import pickle
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from base64 import urlsafe_b64encode
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
from email.mime.application import MIMEApplication
from email import encoders
import mimetypes
import os
from apiclient import errors
from urllib.error import HTTPError
import time
import datetime
import re
import random
SCOPES = ['https://www.googleapis.com/auth/gmail.insert']
creds = None
if os.path.exists('token.pickle'):
with open('token.pickle', 'rb') as token:
creds = pickle.load(token)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('token.pickle', 'wb') as token:
pickle.dump(creds, token)
service = build('gmail', 'v1', credentials=creds)
message = MIMEMultipart('alternative')
part1 = MIMEText('just text', 'plain')
part2 = MIMEText('<b>html text<br><br>html text</b>', 'html')
message.attach(part1)
message.attach(part2)
filename = 'some.attachment'
part = MIMEApplication(open('/var/www/html/attachments/' + filename, 'rb').read())
part.add_header('Content-Disposition', f"attachment; filename=\"{filename}\"; size={os.path.getsize('/var/www/html/attachments/' + filename)}")
part.add_header('Content-Description', f"{filename}")
message.attach(part)
message['to'] = '"Some Body" <somebody#gmail.com>'
message['from'] = 'Some Other <from#email.com>'
message['subject'] = 'test email'
message['Content-Type'] = 'text/html; charset="utf-8"'
message['Content-Transfer-Encoding'] = 'base64'
encoded_message = urlsafe_b64encode(message.as_bytes())
temp = {'raw': encoded_message.decode(), 'labelIds': ['Some_Label', 'UNREAD']}
try:
message1 = service.users().messages().insert(userId='me', body=temp, internalDateSource='dateHeader').execute()
print(f"{message1['id']} / {message1['threadId']}/ {message1}")
except HTTPError as err:
print(err)
Is this a limitation of the insert API or I'm not generating the email with attachment correctly?
Answer:
It appears that this is indeed a limitation of the Gmail API.
More Information:
I did a bit of testing with the API and the Gmail web interface and this behaviour is reproducable in the Gmail UI if the attachment is inserted into the body of the message rather than via the upload attachment button.
This is what is done when the methods messages: insert and messages: send of the Gmail API are used - the attachment is placed in the body of the message rather than attached externally so this is reflected in the UI.
It might be worth noting that the emails are still retrievable in the Gmail UI when using the search term has:attachment, regardless of how the email is placed there (API insert, API send, or standard sending from an email client.
Feature Request:
You can however let Google know that replicating the way attachements are inserted to messages in the UI is a feature that is important for the Gmail API. Google's Issue Tracker is a place for developers to report issues and make feature requests for their development services.
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.
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.