Email attachment received as 'noname' - python

The following Python function results in the attachment being named "noname" when it should be "text_file.txt". As you can see I've tried a 2 different approaches with MIMEBase and MIMEApplication. I've also tried MIMEMultipart('alternative') to no avail.
def send_email(from_addr, to_addr_list,
subject, html_body,plain_text_body,
login,
password,
smtpserver='smtp.gmail.com:587',
cc_addr_list=None,
attachment=None,
from_name=None):
message=MIMEMultipart()
plain=MIMEText(plain_text_body,'plain')
html=MIMEText(html_body,'html')
message.add_header('from',from_name)
message.add_header('to',','.join(to_addr_list))
message.add_header('subject',subject)
if attachment!=None:
#attach_file=MIMEBase('application',"octet-stream")
#attach_file.set_payload(open(attachment,"rb").read())
#Encoders.encode_base64(attach_file)
#f.close()
attach_file=MIMEApplication(open(attachment,"rb").read())
message.add_header('Content-Disposition','attachment; filename="%s"' % attachment)
message.attach(attach_file)
message.attach(plain)
message.attach(html)
server = smtplib.SMTP(smtpserver)
server.starttls()
server.login(login,password)
server.sendmail(from_addr, to_addr_list, message.as_string())
server.quit()
How I'm calling the function:
send_email(
from_addr=from_email,
to_addr_list=["some_address#gmail.com"],
subject=subject,
html_body=html,
plain_text_body=plain,
login=login,
password=password,
from_name=display_name,
attachment="text_file.txt"
)

Your header isn't correct. filename is the attribute not a string.
# Add header to variable with attachment file
attach_file.add_header('Content-Disposition', 'attachment', filename=attachment)
# Then attach to message attachment file
message.attach(attach_file)

Old:
message.add_header('Content-Disposition','attachment; filename="%s"' % attachment)
Update to:
message.add_header('content-disposition', 'attachment',
filename='%s' % 'your_file_name_only.txt' )

I think that this might not be relevant but for those who are interested and getting the same problem :
I am using the google API example too (the one without the attachment) and I realised that the attachment is put only when the text in the subject or the body is NOT a complete string i.e. the string which is being put in the subject or the body is not a single string but an collection of strings.
To explain better :
message = (service.users().messages().send(userId='me', body=body).execute())
body = ("Your OTP is", OTP)
This (body = ("Your OTP is", OTP)) may work for print() command, but it doesn't work for this case. You can change this :
message = (service.users().messages().send(userId='me', body=body).execute())
body = ("Your OTP is", OTP)
to :
CompleteString = "Your OTP is " + OTP
message = (service.users().messages().send(userId='me', body=body).execute())
body = (CompleteString)
The above lines make the 2 parts of the body into a single string.
Also : The 'noname' file that is put as an attachment contains only the written string. So, if you follow this :
message = (service.users().messages().send(userId='me', body=body).execute())
body = ("Your OTP is", OTP)
So all you'll get in the file will be : "Your OTP is "
I am also adding the entire code that I got after modifying the already existing sample code here : https://developers.google.com/gmail/api/quickstart/python
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 email.mime.text import MIMEText
import base64
sender = "sender_mail"
print("Welcome to the Mail Service!")
reciever = input("Please enter whom you want to send the mail to - ")
subject = input("Please write your subject - ")
msg = input("Please enter the main body of your mail - ")
SCOPES = ['https://www.googleapis.com/auth/gmail.modify']
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 = MIMEText(msg)
message['to'] = reciever
message['from'] = sender
message['subject'] = subject
raw = base64.urlsafe_b64encode(message.as_bytes())
raw = raw.decode()
body = {'raw' : raw}
message = (service.users().messages().send(userId='me', body=body).execute())
Please also note that this code only works for text that is put through the mail.
P.S. I am using Python 3.8 so the above code might not work for Python 2.

Related

How to read emails from gmail?

I am trying to connect my gmail to python, but show me this error:
I already checked my password, any idea what can be?
b'[AUTHENTICATIONFAILED] Invalid credentials (Failure)'
Traceback (most recent call last):
File "/Users/myuser/Documents/migrations/untitled3.py", line 29, in read_email_from_gmail
mail.login(FROM_EMAIL,FROM_PWD)
File "/Users/myuser/opt/anaconda3/lib/python3.9/imaplib.py", line 612, in login
raise self.error(dat[-1])
imaplib.IMAP4.error: b'[AUTHENTICATIONFAILED] Invalid credentials (Failure)'
Here my code:
Also i want to know which port I can use?
import smtplib
import time
import imaplib
import email
import traceback
ORG_EMAIL = "#gmail.com"
FROM_EMAIL = "myemail" + ORG_EMAIL
FROM_PWD = "mypassword"
SMTP_SERVER = "smtp.gmail.com"
SMTP_PORT = ??
def read_email_from_gmail():
try:
mail = imaplib.IMAP4_SSL(SMTP_SERVER)
mail.login(FROM_EMAIL,FROM_PWD)
mail.select('inbox')
data = mail.search(None, 'ALL')
mail_ids = data[1]
id_list = mail_ids[0].split()
first_email_id = int(id_list[0])
latest_email_id = int(id_list[-1])
for i in range(latest_email_id,first_email_id, -1):
data = mail.fetch(str(i), '(RFC822)' )
for response_part in data:
arr = response_part[0]
if isinstance(arr, tuple):
msg = email.message_from_string(str(arr[1],'utf-8'))
email_subject = msg['subject']
email_from = msg['from']
print('From : ' + email_from + '\n')
print('Subject : ' + email_subject + '\n')
except Exception as e:
traceback.print_exc()
print(str(e))
read_email_from_gmail()
My main goal is be able to get the CSV file from each email, but for now I just want to read messages.
Best way to read email is using GMAIL API. Google has stopped accessing gmail account using username and password via any outside app from 30 the May 2022.
So this is how you read gmail from python.
What this app does:
Read gmail message (Unread)
Make the message as Read after reading the message
import os.path
import base64
import json
import re
import time
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
import logging
import requests
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly','https://www.googleapis.com/auth/gmail.modify']
def readEmails():
"""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(
# your creds file here. Please create json file as here https://cloud.google.com/docs/authentication/getting-started
'my_cred_file.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())
try:
# Call the Gmail API
service = build('gmail', 'v1', credentials=creds)
results = service.users().messages().list(userId='me', labelIds=['INBOX'], q="is:unread").execute()
messages = results.get('messages',[]);
if not messages:
print('No new messages.')
else:
message_count = 0
for message in messages:
msg = service.users().messages().get(userId='me', id=message['id']).execute()
email_data = msg['payload']['headers']
for values in email_data:
name = values['name']
if name == 'From':
from_name= values['value']
for part in msg['payload']['parts']:
try:
data = part['body']["data"]
byte_code = base64.urlsafe_b64decode(data)
text = byte_code.decode("utf-8")
print ("This is the message: "+ str(text))
# mark the message as read (optional)
msg = service.users().messages().modify(userId='me', id=message['id'], body={'removeLabelIds': ['UNREAD']}).execute()
except BaseException as error:
pass
except Exception as error:
print(f'An error occurred: {error}')
You need to create credential file in Google account
How to create credential file here https://cloud.google.com/docs/authentication/getting-started
Here's the problem, Gmail has a new politic who doesn't allow to use insecure apps.
To help keep your account secure, from May 30, 2022, ​​Google no longer supports the use of third-party apps or devices which ask you to sign in to your Google Account using only your username and password.
You need to enable 'Less secure apps' in your Gmail account if you're going to check it this way. For that reason, it would be better to use the Gmail API.
SMTP port is not set - ensure you are using the correct port (993)
As Carlos pointed out, Google no longer allows insecure apps to access GMail. The 'Less secure apps' option is no longer available. I encountered the same Gmail login issue but I was glad to find the article below and have solved the issue.
https://towardsdatascience.com/how-to-easily-automate-emails-with-python-8b476045c151
smtp.gmail.com is for sending emails, to read the emails you want to connect to imap.gmail.com. It works for me with an App password. I wrote a short application to sync multiple email servers, which is tested with Google mail. Still as mentioned above, it is recommended to use the Google API. On my Github account I have an example application for using the Google API and sorting emails with machine learning, if that is interesting for you.

How to authonticate only one / offline with client.json key for Gmail API?

I am running an app that needs to access the gmail emails. I had store the credentials in the json file and writing pickle file to cache the token. Is it really necessary to authenticate every time you run the application ? I am running application on Pi and DO NOT HAVE an access to it once i deploy that. How can store the cache that i can use for longer time/ multiple time as right now it is going to expire in the middle of the run and turn off my application. I followed the Google API docs but hard to follow that. Here is my code for the authentication would look like(it is same as their documentation. You can comment out the Pi library and its function that would not stop you to run the code.)
Could anyone please suggest a better way to do this ?
# GMail Downloader using the GMail API
# Logs into GMail using the API, checks for Iridium SBD messages every minute,
# saves the message and attachment to file, and moves the message out of the inbox
# Follow these instructions to create your credentials:
# https://developers.google.com/gmail/api/quickstart/python
from __future__ import print_function
import httplib2
import os
import base64
import email
import datetime
import time
from apiclient import discovery
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 oauth2client import client
from oauth2client import tools
from oauth2client.file import Storage
from apiclient import errors
try:
import argparse
flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args()
except ImportError:
flags = None
# Pi libraries
import RPi.GPIO as GPIO
Relay_Ch1 = 26
Relay_Ch2 = 20
Relay_Ch3 = 21
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(Relay_Ch1, GPIO.OUT)
GPIO.setup(Relay_Ch2, GPIO.OUT)
GPIO.setup(Relay_Ch3, GPIO.OUT)
# If modifying these scopes, delete your previously saved credentials
# at ~/.credentials/gmail-python-quickstart.json
#SCOPES = 'https://www.googleapis.com/auth/gmail.readonly' # Read only
# SCOPES = 'https://www.googleapis.com/auth/gmail.modify' # Everything except delete
SCOPES = 'https://mail.google.com/' # Full permissions
# 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.
"""
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)
return service
def ListMessagesMatchingQuery(service, user_id, query=''):
"""List all Messages of the user's mailbox matching the query.
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.
query: String used to filter messages returned.
Eg.- 'from:user#some_domain.com' for Messages from a particular sender.
Returns:
List of Messages that match the criteria of the query. Note that the
returned list contains Message IDs, you must use get with the
appropriate ID to get the details of a Message.
"""
response = service.users().messages().list(userId=user_id,q=query).execute()
messages = []
if 'messages' in response:
messages.extend(response['messages'])
while 'nextPageToken' in response:
page_token = response['nextPageToken']
response = service.users().messages().list(userId=user_id, q=query,pageToken=page_token).execute()
messages.extend(response['messages'])
return messages
def SaveAttachments(service, user_id, msg_id):
"""Get and store attachment from Message with given id.
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.
msg_id: ID of Message containing attachment.
"""
message = service.users().messages().get(userId=user_id, id=msg_id).execute()
local_date = datetime.datetime.fromtimestamp(float(message['internalDate'])/1000.)
date_str = local_date.strftime("%y-%m-%d_%H-%M-%S_")
for part in message['payload']['parts']:
if part['filename']:
if 'data' in part['body']:
data=part['body']['data']
else:
att_id=part['body']['attachmentId']
att=service.users().messages().attachments().get(userId=user_id, messageId=msg_id,id=att_id).execute()
data=att['data']
file_data = base64.urlsafe_b64decode(data.encode('UTF-8'))
path = date_str+part['filename']
with open(path, 'wb') as f:
f.write(file_data)
f.close()
def SaveMessageBody(service, user_id, msg_id):
"""Save the body from Message with given id.
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.
msg_id: ID of Message.
"""
message = service.users().messages().get(userId=user_id, id=msg_id, format='raw').execute()
msg_str = base64.urlsafe_b64decode(message['raw'].encode('ASCII'))
# mime_msg = email.message_from_string(msg_str)
mime_msg = email.message_from_bytes(msg_str)
messageMainType = mime_msg.get_content_maintype()
file_data = ''
#print(messageMainType)
if messageMainType == 'multipart':
for part in mime_msg.get_payload():
partType = part.get_content_maintype()
#print('...'+partType)
if partType == 'multipart':
for multipart in part.get_payload():
multipartType = multipart.get_content_maintype()
#print('......'+multipartType)
if multipartType == 'text':
file_data += multipart.get_payload()
break # Only get the first text payload
elif partType == 'text':
file_data += part.get_payload()
elif messageMainType == 'text':
file_data += mime_msg.get_payload()
local_date = datetime.datetime.fromtimestamp(float(message['internalDate'])/1000.)
date_str = local_date.strftime("%y-%m-%d_%H-%M-%S_")
subject = GetSubject(service, user_id, msg_id);
for c in r' []/\;,><&*:%=+#!#^()|?^': # substitute any invalid characters
subject = subject.replace(c,'_')
path = date_str+subject+".txt"
with open(path, 'w') as f:
f.write(file_data)
f.close()
def GetSubject(service, user_id, msg_id):
"""Returns the subject of the message with given id.
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.
msg_id: ID of Message.
"""
subject = ''
message = service.users().messages().get(userId=user_id, id=msg_id).execute()
payload = message["payload"]
headers = payload["headers"]
for header in headers:
if header["name"] == "Subject":
subject = header["value"]
break
return subject
def MarkAsRead(service, user_id, msg_id):
"""Marks the message with given id as read.
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.
msg_id: ID of Message.
"""
service.users().messages().modify(userId=user_id, id=msg_id, body={ 'removeLabelIds': ['UNREAD']}).execute()
def MoveToLabel(service, user_id, msg_id, dest):
"""Changes the labels of the message with given id to 'move' it.
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.
msg_id: ID of Message.
dest: destination label
"""
# Find Label_ID of destination label
results = service.users().labels().list(userId=user_id).execute()
labels = results.get('labels', [])
for label in labels:
if label['name'] == dest: dest_id = label['id']
# service.users().messages().get(userId='me', id=message['id']).execute()
service.users().messages().modify(userId=user_id, id=msg_id, body={ 'addLabelIds': [dest_id]}).execute()
service.users().messages().modify(userId=user_id, id=msg_id, body={ 'removeLabelIds': ['INBOX']}).execute()
def main():
"""Creates a Gmail API service object.
Searches for unread messages with "SBD Msg From Unit" in the subject.
Saves the message body and attachment to disk.
Marks the message as read.
Moves it to the SBD folder.
"""
# credentials = get_credentials()
# http = credentials.authorize(httplib2.Http())
# service = discovery.build('gmail', 'v1', http=http)
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)
results = service.users().messages().list(userId='me', labelIds = ['INBOX']).execute()
# results = service.users().messages().list(userId='me, q="subject:"SBD Msg From Unit" is:unread has:attachment").execute()
# messages = results.get('messages', [])
messages = ListMessagesMatchingQuery(service, 'me', 'subject:\"Emergency Alert Activated: GSATMicro\" is:unread') # has:attachment
if messages:
for message in messages:
print('Processing: '+GetSubject(service, 'me', message["id"]))
SaveMessageBody(service, 'me', message["id"])
# SaveAttachments(service, 'me', message["id"])
MarkAsRead(service, 'me', message["id"])
# MoveToLabel(service, 'me', message["id"], 'SBD')
#email_subject = message['subject']
#email_from = message['from']
#if email_from == "alerts#gsattrack.com" and email_subject == 'EMERGENCY: GSATMicro - Emergency On':
print("Emergency Alert received from GSATMicro..")
print("\n Help will be on its way .. Turning relay ON.. \n")
#Control the Channel 1
print("\n Turning Relay 1 ON.. \n")
GPIO.output(Relay_Ch1, GPIO.HIGH)
print("Channel 1:The Common Contact is access to the Normal Open Contact!")
time.sleep(0.5)
GPIO.output(Relay_Ch1, GPIO.LOW)
print("Channel 1:The Common Contact is access to the Normal Closed Contact!\n")
time.sleep(0.5)
GPIO.output(Relay_Ch1, GPIO.HIGH)
#else:
#print('No messages found!')
if __name__ == '__main__':
print('GMail API Downloader')
print('Press Ctrl-C to quit')
try:
while True:
#print('Checking for messages...')
main()
for i in range(60):
time.sleep(1) # Sleep
except KeyboardInterrupt:
print('Ctrl-C received!')

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

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

How to save a gmail draft with inline images?

I want to use a python script to save a gmail draft containing inline images and rendered html text, so that it can be checked before being sent manually.
It seems the cid protocol for attaching inline images doe not work when SENDING the email, even if the draft looks good.
import pickle
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
import base64
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 apiclient import errors
# If modifying these scopes, delete the file token.pickle.
SCOPES = ['https://mail.google.com/']
def CreateDraft(user_id, message_body):
"""Create and insert a draft email.
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_body: The body of the email message, including headers.
Returns:
Draft object, including draft id and message meta data.
"""
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()
# Save the credentials for the next run
with open('token.pickle', 'wb') as token:
pickle.dump(creds, token)
service = build('gmail', 'v1', credentials=creds)
try:
message = {'message': message_body}
draft = service.users().drafts().create(userId=user_id, body=message).execute()
return draft
except errors.HttpError as error:
print ('An error occurred: %s' % error)
return None
def CreateMessageWithAttachment(sender, to, subject, message_text, file_dir, filename):
"""Create a message for an email.
Args:
* sender: The email address of the sender.
* to: The email address of the receiver.
* subject: The subject of the email message.
* message_text: The html text of the email message.
* file_dir: The directory containing the file to be attached.
* filename: The name of the file to be attached.
Returns:
An object containing a base64url encoded email object.
"""
message = MIMEMultipart()
message['to'] = to
message['from'] = sender
message['subject'] = subject
msg = MIMEText(message_text,'html')
message.attach(msg)
path = os.path.join(file_dir, filename)
content_type, encoding = mimetypes.guess_type(path)
if content_type is None or encoding is not None:
content_type = 'application/octet-stream'
main_type, sub_type = content_type.split('/', 1)
if main_type == 'text':
fp = open(path, 'rb')
msg = MIMEText(fp.read(), _subtype=sub_type)
fp.close()
elif main_type == 'image': #The only useful one for images
fp = open(path, 'rb')
msg = MIMEImage(fp.read(), _subtype=sub_type)
fp.close()
elif main_type == 'audio':
fp = open(path, 'rb')
msg = MIMEAudio(fp.read(), _subtype=sub_type)
fp.close()
else:
fp = open(path, 'rb')
msg = MIMEBase(main_type, sub_type)
msg.set_payload(fp.read())
fp.close()
msg.add_header('Content-Disposition', 'attachment', filename=filename)
message.attach(msg)
return {'raw': base64.urlsafe_b64encode(message.as_bytes()).decode()}
This script creates a draft with an attached file. However, if the file is an image I would like it to be inline instead of an attachement...

How do I send HTML Formatted emails, through the gmail-api for python

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)

Categories

Resources