Related
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}>,...)
I have made a development who let me to send emails. If I try to send an email to one receipt there is no problem. But, If I try to send the same email to more than one receipts then I've got an error. I don't want to send an email to several receipts, what I want is to send one email to each of the receipts.
The error that I've got is:
File "/home/josecarlos/Workspace/python/reports/reports/pregame.py", line 22, in __init__
self.send_mail(subject, message, fileName)
File "/home/josecarlos/Workspace/python/reports/reports/report.py", line 48, in send_mail
message = mail.create_message()
File "/home/josecarlos/Workspace/python/reports/com/mail/mail.py", line 100, in create_message
message.attach(MIMEText(self.params["message"], "plain"))
File "/usr/lib/python3.6/email/mime/text.py", line 34, in __init__
_text.encode('us-ascii')
AttributeError: 'dict' object has no attribute 'encode'
To send an email I have this class:
import base64
import logging
import os
import os.path
import pickle
from email import encoders
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient import errors
from googleapiclient.discovery import build
class Mail:
def __init__(self, params):
'''
:param params: It's a dictionary with these keys:
from: Email account from the email is sended
to: Email account who will receive the email
subject: Subject of the email
message: Message of the email.
game: Next games
'''
self.params = params
#staticmethod
def get_service():
"""Gets an authorized Gmail API service instance.
Returns:
An authorized Gmail API service instance..
"""
# If modifying these scopes, delete the file token.pickle.
SCOPES = [
#'https://www.googleapis.com/auth/gmail.readonly',
'https://www.googleapis.com/auth/gmail.send',
]
creds = None
# The file token.pickle 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.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(
'com/mail/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)
return service
#staticmethod
def send_message(service, sender, message):
"""Send an email message.
Args:
service: Authorized Gmail API service instance.
sender: 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:
sent_message = (service.users().messages().send(userId=sender, body=message)
.execute())
logging.info('Message Id: %s', sent_message['id'])
return sent_message
except errors.HttpError as error:
logging.error('An HTTP error occurred: %s', error)
def create_message(self):
"""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 = MIMEMultipart()
message['from'] = self.params["from"]
message['to'] = self.params["to"]
message['subject'] = self.params["subject"]
message.attach(MIMEText(self.params["message"], "plain"))
routeFile = self.params["routeFile"] + self.params["fileName"]
fileName = self.params["fileName"]
# Open PDF file in binary mode
with open(routeFile, "rb") as attachment:
# Add file as application/octet-stream
# Email client can usually download this automatically as attachment
part = MIMEBase("application", "octet-stream")
part.set_payload(attachment.read())
# Encode file in ASCII characters to send by email
encoders.encode_base64(part)
# Add header as key/value pair to attachment part
part.add_header(
"Content-Disposition",
f"attachment; filename= {fileName}",
)
# Add attachment to message and convert message to string
message.attach(part)
s = message.as_string()
b = base64.urlsafe_b64encode(s.encode('utf-8'))
return {'raw': b.decode('utf-8')}
To send an email, I'll do it with this method:
def send_mail(self, subject, message, fileName):
args = self.params["destiny"]
if self.params["competition"] == COMPETITIONS.LF1 or self.params["competition"] == COMPETITIONS.LF2:
data = SearchData(args, "subscriptors.emails=")
else:
data = SearchDataFIBA(args, "subscriptors.emails=")
emails = data.get_result().getData()
for item in emails:
print(f"Enviamos informe a la cuenta: {item['email']}")
params = {
"from" : "basketmetrics#gmail.com",
"to" : item["email"],
"subject": subject,
"message" : message,
"fileName" : fileName,
"routeFile" : f"output/reports/{self.params['destiny']}/"
}
mail = Mail(params)
message = mail.create_message()
service = mail.get_service()
mail.send_message(service, "basketmetrics#gmail.com", message)
My application has secure access to google account.
I don't know how I cannot send more than an email in a row when I have no
problem to send only one e-mail.
Am I doing something wrong?
Edit I:
To reproduce the error, you can test it with this test code:
import unittest
import os
from com.mail.mail import Mail
class TestSendMail(unittest.TestCase):
def setUp(self) -> None:
os.chdir(os.path.abspath(os.path.join(os.path.dirname( __file__ ), '..')))
def test_send_mail(self):
message = "¡¡¡Hola!!!\n\nOs enviamos el informe pre partido previo a vuestro próximo partido.\n\nSaludos,\n\nBasketmetrics.com"
subject = "Informe pre partido"
fileName = "name_of_the_file"
emails = [{"email" : "receipt1#gmail.com"}, {"email" : "receipt2#gmail.com"}]
for item in emails:
print(f"Enviamos informe a la cuenta: {item['email']}")
params = {
"from" : "sender#gmail.com",
"to" : item["email"],
"subject": subject,
"message" : message,
"fileName" : fileName,
"routeFile" : "route to the file"
}
mail = Mail(params)
message = mail.create_message()
service = mail.get_service()
mail.send_message(service, "sender#gmail.com", message)
Also, you have to change some values for your own values and the file credentials.json file of your own google account
Edit II:
I have found where it produces the error but not why. The problem comes up when I invoque for second time the class Mail. In that moment I pass some parameters to the constructor with params variable. In that variable I pass the text of the message. This message is create outside of the loop.
If I read the first 120 characters of what receives the Mail class in params["message"] in the constructor:
def __init__(self, params):
'''
:param params: It's a dictionary with these keys:
from: Email account from the email is sended
to: Email account who will receive the email
subject: Subject of the email
message: Message of the email.
game: Next games
'''
self.params = params
print(f"message received: {params['message'][:120]}")
For the first time, I've got the content of message variable:
message received: ¡¡¡Hola!!!
Os enviamos el informe pre partido previo a vuestro próximo partido.
Saludos,
Basketmetrics.com
But the second time, I should receive the same text!!! But I receive an error:
Error
Traceback (most recent call last):
File "/usr/lib/python3.6/unittest/case.py", line 59, in testPartExecutor
yield
File "/usr/lib/python3.6/unittest/case.py", line 605, in run
testMethod()
File "/home/josecarlos/Workspace/python/reports/test/test_send_mail.py", line 26, in test_send_mail
mail = Mail(params)
File "/home/josecarlos/Workspace/python/reports/com/mail/mail.py", line 27, in __init__
print(f"message received: {params['message'][:120]}")
TypeError: unhashable type: 'slice'
If I read all the message without any limit or characers. For the first time, I receive the content of the variable message. But in the second time, I receive a very big string os characters, here is a little example:
lGWFBVZzhQc3pDNWdiNmhnMW1odDZHcmlFZWsyClZvTTBRT1R2dXpWaWlyZkpleS9mZXhtR3V3V2hTV0JjWERtSUNnWENTQVJ1QjdrN1Nzd3BrZ0c1Rkl3MXVDMmNyZk95ZUhySVM1dHQKSUh2T1YvWW1Pd2YzL3B2WEpLaEMza
Why I receive this string of charanters instead of the value of message variable?
I have check it that If I put message variable inside the for loop, instead of outside of the loop ... It works!!! I receive the two mails!!!
But, this solution isn't usefull because I want to reuse my code and I need to pass some values through variables.
So, why in the second time I don't receive the value of message variable and I receive a long string of characters?
How can I fix this error? Why it happens this error?
Edit III:
Checking the type of the value that I receive in the constructor of Mail, for the first time is "string":
typeof: <class 'str'>
But in the second time is "dict":
typeof: <class 'dict'>
And checking the keys of self.params["message"] are:
keys: dict_keys(['raw'])
I dont't understand anything ... How is it possible that params["message"] has the value of message variable and the second time params["message"] has modified its type to raw?
Edit IV:
I have modified the content of message variable from ...
message= ""
To ...
mesage = "¡¡¡Hola!!!0x0a0x0aOs enviamos el informe pre partido previo a vuestro próximo partido.0x0a0x0aSaludos,0x0a0x0aBasketmetrics.com"
But it doesn't work. I've got the same error.
Edit V:
I have modified the contento of message variable. Now, instead of plain text, I'm going to send an html ...
message = """\
<html>
<head></head>
<body>
<p>
Hi, this is a test!!!
</p>
<p>
Best regards!!!
</p>
</body>
</html>
"""
To do send this message, you have to modify this instruction in method create_message of Mail class:
message.attach(MIMEText(self.params["message"], "plain"))
to this:
message.attach(MIMEText(self.params["message"], "html"))
And ... I've got the same error!!!
I don't know what to do anymore ...
Edit VI:
Last attempt ... I have modified the text of the message removing "strange" character like "" or "<" and I have send a simple text with the message "Hello".
So, my message variable now is:
message = "Hello"
And I have modified again the format of the email, from "html" to "plain"
And ... I've got the same error in my second email!!!
This is frustrating ... :((((((
You have:
def send_mail(self, subject, message, fileName):
Where argument message is the message text to be sent. But we have in your function `test_send_mail':
def test_send_mail(self):
message = "¡¡¡Hola!!!\n\nOs enviamos el informe pre partido previo a vuestro próximo partido.\n\nSaludos,\n\nBasketmetrics.com"
# code omitted
for item in emails:
# code omitted
message = mail.create_message() # overlaying message text with return value from create_message
service = mail.get_service()
mail.send_message(service, "sender#gmail.com", message)
In the for item in emails: loop, you have overlaid message with the return value from the call to mail.create_message(), so for the next iteration message is not a string anymore. This, I believe is your problem. You need to use a different variable name for either the return value from the call to mail.create_message() or for the message text.
So the best solution would be to have a small list of all the email ids, and run through the for loop and you can send the mails, Or you can actually use this package from python to help you assist you in your work,
A simple example would be like this
def send_mail(names, emails, senderMail, password, subject):
port = 587
password = password
total = len(names)
server = smtplib.SMTP('smtp.gmail.com', port)
server.starttls()
server.login(senderMail, password)
cc = 0
for i in range(len(emails)):
body = '' #body of the mail, you can actually add html also please check the documentation
msg = MIMEMultipart()
msg['From'] = senderMail
msg['To'] = emails[i]
msg['Subject'] = subject
msg.attach(MIMEText(body, 'plain'))
text = msg.as_string()
server.sendmail(senderMail,emails[i], text)
cc += 1
print(cc , " / ", total)
server.quit()
If you want a easier approach attaching images you can check this package also,
Please check this github repo for detailed work flow
I am trying to run a file (asana_get_user.py) in C:\Users\WilsonNg\Documents\Internship via python.
However, when I run it, I get an attribute error:
error message
However, there is another exception error happening after this code is finished for some reason:
exception error
So it opened another file (email.py) within the same folder and created an exception error.
Any idea on why it referenced my email.py file?
asana_get_user.py
import asana
# replace with your personal access token.
personal_access_token = '0/2c...'
# Construct an Asana client
client = asana.Client.access_token(personal_access_token)
# Set things up to send the name of this script to us to show that you succeeded! This is optional.
client.options['client_name'] = "hello_world_python"
# Get your user info
me = client.users.me()
# Print out your information
print ("Hello world! " + "My name is " + me['name'] + " and I my primary Asana workspace is " + me['workspaces'][0]['name'] + ".")
email.py
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 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)
subject = "This is a test subject"
message = "This is a test content"
sent_to = "test#test.com"
sender = "test#test.com"
sending_msg = create_message(sender,sent_to,subject,message)
send_message()
Please put a check in email.py as if __name__ == '__main__': before subject = "This is a test subject".
email.py is being run as the import asana is called.
if __name__ == '__main__':
subject = "This is a test subject"
message = "This is a test content"
sent_to = "test#test.com"
sender = "test#test.com"
sending_msg = create_message(sender,sent_to,subject,message)
send_message()
Read about it here.
Using the sample code from the GMail API Example: Send Mail, and after following rules for authentication, it's simple enough to send a programmatically generated email, via a gmail account. What isn't obvious from the example is how to set that email to be HTML formatted.
The Question
How do I get HTML formatting in my gmail-api send messages, using python?
I have this...
message_body = "Hello!\nYou've just received a test message!\n\nSincerely,\n-Test Message Generator\n"
and I want it to be this...
Hello!
You've just received a test message!
Sincerely,
-Test Message Generator
Example Source Code from GMail-API
Below is a slightly modified version of the example, but still works:
import argparse
import base64
from pprint import pformat
from pprint import pprint
import httplib2
import os
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
from apiclient import discovery
from oauth2client import client
from oauth2client import tools
from oauth2client.file import Storage
SCOPES = 'https://mail.google.com/'
CLIENT_SECRET_FILE = 'client_secret.json'
APPLICATION_NAME = 'Test EMail App'
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 = 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, cc, 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.
"""
print(sender + ', ' + to + ', ' + subject + ', ' + message_text)
message = MIMEText(message_text)
message['to'] = to
message['from'] = sender
message['subject'] = subject
message['cc'] = cc
pprint(message)
return {'raw': base64.urlsafe_b64encode(message.as_string())}
def send_message(service, user_id, message_in):
"""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.
"""
pprint(message_in)
try:
message = (service.users().messages().send(userId=user_id, body=message_in).execute())
pprint(message)
print ('Message Id: %s' % message['id'])
return message
except errors.HttpError, error:
print ('An error occurred: %s' % error)
def main(cli):
"""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)
email_msg = create_message(cli.addr_from, cli.addr_to, cli.addr_cc, cli.subject, cli.message)
msg_out = service.users().messages().send(userId = 'me', body = email_msg).execute()
pprint(msg_out)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-m', '--message', help = 'The message to send in the email', default='<MESSAGE = unfinished>')
parser.add_argument('-t', '--addr_to', help = 'the list of comma separated emails to send', default='cbsd.tools#gmail.com')
parser.add_argument('-s', '--subject', help = 'the email subject', default='<SUBJECT = undefined>')
parser.add_argument('-c', '--addr_cc', help = 'email CC\'s', default='')
parser.add_argument('-f', '--addr_from', help = 'Email address to send from', default='cbsd.tools#gmail.com')
cli = parser.parse_args()
pprint(dir(cli))
main(cli)
Tried as I might, with this code, and variations on it, I could not get html formatted code, nor could I get simple escape characters to create carriage returns where they needed to be.
Here's what didn't work
Trying the following didn't work either:
modifying line 69 to add additional message dictionary parameters... ie.
{'raw': base64.urlsafe_b64encode(message.as_string()), 'payload': {'mimeType': 'text/html'}}
As documented here in the GMail API Docs
Adding a variety of escaped backslashes into the message text:
\n... ie: \\n nor \\\n and just rendered as those exact characters
Adding <br> </br> <br/> did not add new lines and just rendered as those exact characters
Adding \r did not add new lines and just rendered as that exact character
Reasons this is not a duplicate question
This SO link deals with multipart/signed messages
I am uninterested in multipart messaging because it is an inelegant solution. I want the WHOLE message to be HTML.
Additionally, this link has no accepted answer, and the one answer present is a non-answer as it simply states that they are sending a problem statement to Google.
This one deals with c#, which I am not interested in since I'm writing in python
This one also uses SMTP which I'm not interested in
This one is for Rails
After doing a lot of digging around, I started looking in to the python side of the message handling, and noticed that a python object is actually constructing the message to be sent for base64 encoding into the gmail-api message object constructor.
See line 63 from above: message = MIMEText(message_text)
The one trick that finally worked for me, after all the attempts to modify the header values and payload dict (which is a member of the message object), was to set (line 63):
message = MIMEText(message_text, 'html') <-- add the 'html' as the second parameter of the MIMEText object constructor
The default code supplied by Google for their gmail API only tells you how to send plain text emails, but they hide how they're doing that.
ala...
message = MIMEText(message_text)
I had to look up the python class email.mime.text.MIMEText object.
That's where you'll see this definition of the constructor for the MIMEText object:
class email.mime.text.MIMEText(_text[, _subtype[, _charset]])
We want to explicitly pass it a value to the _subtype. In this case, we want to pass: 'html' as the _subtype.
Now, you won't have anymore unexpected word wrapping applied to your messages by Google, or the Python mime.text.MIMEText object
The Fixed Code
def create_message(sender, to, cc, 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.
"""
print(sender + ', ' + to + ', ' + subject + ', ' + message_text)
message = MIMEText(message_text,'html')
message['to'] = to
message['from'] = sender
message['subject'] = subject
message['cc'] = cc
pprint(message)
return {'raw': base64.urlsafe_b64encode(message.as_string())}
Try this:
def CreateMessage(emailSubject, emailTo, emailFrom, message_body, emailCc, html_content=None):
try:
message = MIMEMultipart('alternative')
message['to'] = emailTo
message['from'] = emailFrom
message['subject'] = emailSubject
message['Cc'] = emailCc
body_mime = MIMEText(message_body, 'plain')
message.attach(body_mime)
if html_content:
html_mime = MIMEText(html_content, 'html')
message.attach(html_mime)
return {
'raw': base64.urlsafe_b64encode(
bytes(
message.as_string(),
"utf-8")).decode("utf-8")}
except Exception as e:
print('Error in CreateMessage()', e)
return '400'
The accepted answer works, but in my journey down this rabbit hole, I found the right place to put the payload from the original question, so here's a full example. I'm using a service account with domain-wide delegation:
import base64
from googleapiclient.discovery import build
from google.oauth2 import service_account
from email.message import EmailMessage
SCOPES = ['https://www.googleapis.com/auth/gmail.send']
CREDS = service_account.Credentials.from_service_account_file(
serviceAcct, scopes=SCOPES, subject=senderEmail)
with build('gmail', 'v1', credentials=CREDS) as service:
msg = EmailMessage()
content="Message body in <b>html</b> format!"
msg['To'] = recipientEmail
msg['From'] = senderEmail
msg['Subject'] = 'Gmail API test'
# Use this for plain text
# msg.set_content(content)
# Otherwise, use this for html
msg.add_header('Content-Type','text/html')
msg.set_payload(content)
encodedMsg = base64.urlsafe_b64encode(msg.as_bytes()).decode()
try:
sendMsg = service.users().messages().send(
userId=senderEmail,
body={ 'raw': encodedMsg }
).execute()
print('Msg id:', sendMsg['id'])
except Exception as e:
print('Error:', e)
Google are pushing us to improve the security of script access to their gmail smtp servers. I have no problem with that. In fact I'm happy to help.
But they're not making it easy. It's all well and good to suggest we Upgrade to a more secure app that uses the most up to date security measures, but that doesn't help me work out how to upgrade bits of code that look like this:
server = smtplib.SMTP("smtp.gmail.com", 587)
server.ehlo()
server.starttls()
server.login(GMAIL_USER, GMAIL_PASSWORD)
server.sendmail(FROM, TO, MESSAGE)
server.close()
Sure, I'll go and turn on "Access for less secure apps", but if anyone has worked out what to replace this code with, I'll be grateful.
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 select Create New Client ID
Choose Installed Application as the application type and Other
You should now have a button Download JSON. Do that. It's your client_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'] = "yourvictim#goes.here"
message['from'] = "you#go.here"
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("send#gmail.com", "receive#gmail.com", "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.
Updated sample for Python 3, and GMail's current API, below.
Note that to get the credentials.json file below, you'll need to create an Oauth client ID credential here, after selecting the relevant GCP project. Once you've created it you'll be shown the client key and client secret. Close that prompt, and click the down arrow next to the account. This is the file you'll need.
import base64
import logging
import mimetypes
import os
import os.path
import pickle
from email.mime.text import MIMEText
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient import errors
from googleapiclient.discovery import build
def get_service():
"""Gets an authorized Gmail API service instance.
Returns:
An authorized Gmail API service instance..
"""
# If modifying these scopes, delete the file token.pickle.
SCOPES = [
'https://www.googleapis.com/auth/gmail.readonly',
'https://www.googleapis.com/auth/gmail.send',
]
creds = None
# The file token.pickle 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.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)
return service
def send_message(service, sender, 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:
sent_message = (service.users().messages().send(userId=sender, body=message)
.execute())
logging.info('Message Id: %s', sent_message['id'])
return sent_message
except errors.HttpError as error:
logging.error('An HTTP 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
s = message.as_string()
b = base64.urlsafe_b64encode(s.encode('utf-8'))
return {'raw': b.decode('utf-8')}
if __name__ == '__main__':
logging.basicConfig(
format="[%(levelname)s] %(message)s",
level=logging.INFO
)
try:
service = get_service()
message = create_message("from#gmail.com", "to#gmail.com", "Test subject", "Test body")
send_message(service, "from#gmail.com", message)
except Exception as e:
logging.error(e)
raise
Have you considered using the Gmail API? The API has security features built in and is optimized specifically for Gmail. You can find the API documentation on http://developers.google.com - for example, here's the documentation for the Send API call:
https://developers.google.com/gmail/api/v1/reference/users/messages/send
I'm including some code that has been updated for python 3 usage - it seems to send emails once you get the necessary permissions and OAuth tokens working. It's mostly based off the Google API website samples.
from __future__ import print_function
import base64
import os
from email.mime.text import MIMEText
import httplib2
from apiclient import discovery
from googleapiclient import errors
from oauth2client import client
from oauth2client import tools
from oauth2client.file import Storage
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 = 'https://www.googleapis.com/auth/gmail.send'
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.
"""
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 = 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
to = 'test#email.com'
sender = 'test#email.com'
subject = 'test emails'
message_text = 'hello this is a text test message'
user_id = 'me'
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_bytes()).decode())}
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: {}'.format(message['id']))
return message
except errors.HttpError as error:
print('An error occurred: {}'.format(error))
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)
msg = create_message(sender,to,subject,message_text)
message = (service.users().messages().send(userId=user_id, body=msg)
.execute())
print('Message Id: {}'.format(message['id']))
results = service.users().messages().list(userId='me').execute()
labels = results.get('labels', [])
if not labels:
print('No labels found.')
else:
print('Labels:')
for label in labels:
print(label['name'])
if __name__ == '__main__':
main()
it is very simple.
Go to Gmail account settings.
Search for "App passwords".
Generate an app password.
Use that password instead of the Gmail password.
The advantage of app password is that you don't need to turn off two factor authentication and also you don't need to allow insecure apps.
This is the official link about Google app passwords https://support.google.com/mail/answer/185833?hl=en-GB
Hope this helps👍
protected string SendEmail(string toAddress, string subject, string body)
{
string result = "Message Sent Successfully..!!";
string senderID = "...........";// use sender's email id here..
const string senderPassword = "........."; // sender password here...
try
{
SmtpClient smtp = new SmtpClient
{
Host = "smtp.gmail.com", // smtp server address here...
Port = 587,
EnableSsl = true,
DeliveryMethod = SmtpDeliveryMethod.Network,
Credentials = new System.Net.NetworkCredential(senderID, senderPassword),
Timeout = 30000,
};
MailMessage message = new MailMessage(senderID, toAddress, subject, body);
smtp.Send(message);
}
catch (Exception ex)
{
result = "Error sending email.!!!";
}
return result;
}