Gmail & Python, send different emails with same attachment - python

I need to send multiple emails (like 200 customized emails each day), but all have same pdf attachment. Is it possible to upload the attachment only once to save on upload time?
Even better than that, is it possible to upload the file only once on a google server and each day just reference that file?
Just for reference here is the code (modified a bit from google developer sample code):
# main function
def SendMessageAttachment(sender, to, subject, msgHtml, msgPlain, attachmentFile):
credentials = get_credentials()
http = credentials.authorize(httplib2.Http())
service = discovery.build('gmail', 'v1', http=http)
message1 = create_message_with_attachment(sender, to, subject, msgPlain, attachmentFile)
SendMessageInternal(service, "me", message1)
def SendMessageInternal(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, error:
print 'An error occurred: %s' % error
def create_message_with_attachment(
sender, to, subject, message_text, attachmentFile):
"""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.
file: The path to 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)
message.attach(msg)
print "create_message_with_attachment: file:", attachmentFile
content_type, encoding = mimetypes.guess_type(attachmentFile)
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(attachmentFile, 'rb')
msg = MIMEText(fp.read(), _subtype=sub_type)
fp.close()
elif main_type == 'image':
fp = open(attachmentFile, 'rb')
msg = MIMEImage(fp.read(), _subtype=sub_type)
fp.close()
elif main_type == 'audio':
fp = open(attachmentFile, 'rb')
msg = MIMEAudio(fp.read(), _subtype=sub_type)
fp.close()
else:
fp = open(attachmentFile, 'rb')
msg = MIMEBase(main_type, sub_type)
msg.set_payload(fp.read())
fp.close()
filename = os.path.basename(attachmentFile)
msg.add_header('Content-Disposition', 'attachment', filename=filename)
message.attach(msg)
return {'raw': base64.urlsafe_b64encode(message.as_string())}

the attachment is loaded here:
part = MIMEApplication(open("mypdf.pdf","rb").read())
but the reference for the header can be anywhere
part.add_header('Content-Disposition', 'attachment', filename="file.pdf")
msg.attach(part)
You could write a function to add this header before sending the mail and iterate over all your recipients.

Related

.txt to HTML string with variables python

I have a script to send emails using Gmail's API:
if __name__ == '__main__':
service = get_service()
user_id = 'me'
sender = 'myemail#gmail.com'
recipients_list = ['to#gmail.com']
var1 = input('Type whatever here: ')
subject = f'Reasonable subject in {var1}'
body = f'<h1>Hello World<\h1><p>{var1}'
attached_file = r'C:\Somefile.pdf'
for item in recipients_list:
msg = create_message_with_attachment(sender, subject=subject, body=body, file=attached_file, to=item)
send_message(service, user_id, msg)
I've managed to get the string of the variable body from a .txt file using Pathlib but I can't figure out how to make so the email sent in interpreted as HTML as well as var1 be interpreted as a variable instead of part of the sting. How could I achieve this?
EDIT:
Sorry, I realize I've misexplained. I meant the HTML part is fine, but the .txt file with the body of the email includes variables (i.e. var1) that I need to be interpreted as such instead of part of the string.
I.e.:
Hello World
How can make this a {var1}
Also here's the function that creates the email:
def create_message_with_attachment(sender, to, subject, body, file):
message = MIMEMultipart()
message['to'] = to
message['from'] = sender
message['subject'] = subject
msg = MIMEText(body, 'html')
message.attach(msg)
(content_type, encoding) = mimetypes.guess_type(file)
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':
with open(file, 'rb') as f:
msg = MIMEText(f.read().decode('utf-8'), _subtype=sub_type)
elif main_type == 'image':
with open(file, 'rb') as f:
msg = MIMEImage(f.read(), _subtype=sub_type)
elif main_type == 'audio':
with open(file, 'rb') as f:
msg = MIMEAudio(f.read(), _subtype=sub_type)
else:
with open(file, 'rb') as f:
msg = MIMEBase(main_type, sub_type)
msg.set_payload(f.read())
filename = os.path.basename(file)
msg.add_header('Content-Disposition', 'attachment', filename=filename)
email.encoders.encode_base64(msg)
message.attach(msg)
raw_msg = base64.urlsafe_b64encode(message.as_string().encode('utf-8'))
return {'raw': raw_msg.decode('utf-8')}
When creating the message, you should add an additional html parameter to the MIMEText class.
Therefore, your code will look something like this
subject = 'Reasonable subject in' + var2
body = '<h1>Hello World<\h1>'+ var1
And when creating the email message, it should have a structure similar to this - notice the addition of the html parameter.
message = MIMEText(message, 'html')
message['to'] = to
message['from'] = sender
message['subject'] = subject
encodedmsg = urlsafe_b64encode(bytes(message))
result = {
'raw': encodedmsg.decode()
}
Reference
Python email: Examples.

How to send Bcc in Python Google API?

I'm using the code described in:
Sending email via gmail & python
to send e-mails. Everything works great. Now I'd like to add a Bcc (one or more) and I don't know how.
If I add: message['Bcc'] = 'mail#mail.com' it works but the senders see the Bcc, and I don't want that.
I know that using SMTP you can:
smtp = smtplib.SMTP('smtp.gmail.com', 587)
smtp.sendmail(user, [to] + bcc, message.as_string())
but I don't want to do it that way.
How can I add Bcc without the sender knowing about it, using google google_auth_oauthlib?
This is the code I'm using
import httplib2
import os
import oauth2client
from oauth2client import client, tools, file
import base64
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from apiclient import errors, discovery
import mimetypes
from email.mime.image import MIMEImage
from email.mime.audio import MIMEAudio
from email.mime.base import MIMEBase
SCOPES = 'https://www.googleapis.com/auth/gmail.send'
CLIENT_SECRET_FILE = 'client_secret.json'
APPLICATION_NAME = 'Gmail API Python Send Email'
def get_credentials():
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-email-send.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
credentials = tools.run_flow(flow, store)
print('Storing credentials to ' + credential_path)
return credentials
def SendMessage(sender, to, subject, msgHtml, msgPlain, attachmentFile=None):
credentials = get_credentials()
http = credentials.authorize(httplib2.Http())
service = discovery.build('gmail', 'v1', http=http)
if attachmentFile:
message1 = createMessageWithAttachment(sender, to, subject, msgHtml, msgPlain, attachmentFile)
else:
message1 = CreateMessageHtml(sender, to, subject, msgHtml, msgPlain)
result = SendMessageInternal(service, "me", message1)
return result
def SendMessageInternal(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)
return "Error"
return "OK"
def CreateMessageHtml(sender, to, subject, msgHtml, msgPlain):
msg = MIMEMultipart('alternative')
msg['Subject'] = subject
msg['From'] = sender
msg['To'] = to
msg.attach(MIMEText(msgPlain, 'plain'))
msg.attach(MIMEText(msgHtml, 'html'))
return {'raw': base64.urlsafe_b64encode(msg.as_string())}
def createMessageWithAttachment(
sender, to, subject, msgHtml, msgPlain, attachmentFile):
"""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.
msgHtml: Html message to be sent
msgPlain: Alternative plain text message for older email clients
attachmentFile: The path to the file to be attached.
Returns:
An object containing a base64url encoded email object.
"""
message = MIMEMultipart('mixed')
message['to'] = to
message['from'] = sender
message['subject'] = subject
messageA = MIMEMultipart('alternative')
messageR = MIMEMultipart('related')
messageR.attach(MIMEText(msgHtml, 'html'))
messageA.attach(MIMEText(msgPlain, 'plain'))
messageA.attach(messageR)
message.attach(messageA)
print("create_message_with_attachment: file: %s" % attachmentFile)
content_type, encoding = mimetypes.guess_type(attachmentFile)
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(attachmentFile, 'rb')
msg = MIMEText(fp.read(), _subtype=sub_type)
fp.close()
elif main_type == 'image':
fp = open(attachmentFile, 'rb')
msg = MIMEImage(fp.read(), _subtype=sub_type)
fp.close()
elif main_type == 'audio':
fp = open(attachmentFile, 'rb')
msg = MIMEAudio(fp.read(), _subtype=sub_type)
fp.close()
else:
fp = open(attachmentFile, 'rb')
msg = MIMEBase(main_type, sub_type)
msg.set_payload(fp.read())
fp.close()
filename = os.path.basename(attachmentFile)
msg.add_header('Content-Disposition', 'attachment', filename=filename)
message.attach(msg)
return {'raw': base64.urlsafe_b64encode(message.as_string())}
def main():
to = "to#address.com"
sender = "from#address.com"
subject = "subject"
msgHtml = "Hi<br/>Html Email"
msgPlain = "Hi\nPlain Email"
SendMessage(sender, to, subject, msgHtml, msgPlain)
# Send message with attachment:
SendMessage(sender, to, subject, msgHtml, msgPlain, '/path/to/file.pdf')
if __name__ == '__main__':
main()

Replying to an email using Gmail API with Python

I'm trying to send an reply for an email using Gmail API. I wrote the following code and it does not reply for the email but send as a new mail.
def create_message_with_attachment(
sender, to,cc, subject, message_text, file):
"""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.
file: The path to 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
message['cc'] = cc
msg = MIMEText(message_text)
message.attach(msg)
content_type, encoding = mimetypes.guess_type(file)
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(file, 'rb')
msg = MIMEText(fp.read(), _subtype=sub_type)
fp.close()
elif main_type == 'image':
fp = open(file, 'rb')
msg = MIMEImage(fp.read(), _subtype=sub_type)
fp.close()
elif main_type == 'audio':
fp = open(file, 'rb')
msg = MIMEAudio(fp.read(), _subtype=sub_type)
fp.close()
else:
fp = open(file, 'rb')
msg = MIMEBase(main_type, sub_type)
msg.set_payload(fp.read())
fp.close()
filename = os.path.basename(file)
msg.add_header('Content-Disposition', 'attachment', filename=filename)
message.attach(msg)
encoders.encode_base64(msg)
encoded_message = urlsafe_b64encode(message.as_bytes())
raw_msg= {'raw': encoded_message.decode()}
raw_msg['threadId']= '16a7c412848d632d'
return raw_msg
The sent email shows under the thread in my mailbox, but for the receiver it sent as a new mail, not under the mail he sent earlier.

Error 10053 When Sending Large Attachments using Gmail API

I'm trying to send emails of various sizes using the Gmail API and the functions below.
Generally this works perfectly, however for attachments over around 10MB (which are rare but will happen) I recieve Errno 10053 which I think is because I timeout when sending the message including the large attachment.
Is there a way around this by say, specifying size or increasing the timeout limit? There's reference to size in the Gmail API docs, but I'm struggling to understand how to use in Python or whether it would even help.
def CreateMessageWithAttachment(sender, to, cc, subject,
message_text, file_dir, filename):
"""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.
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
if cc != None:
message['cc'] = cc
message['from'] = sender
message['subject'] = subject
msg = MIMEText(message_text)
message.attach(msg)
path = os.path.join(file_dir, filename)
content_type, encoding = mimetypes.guess_type(path)
QCoreApplication.processEvents()
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':
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()
QCoreApplication.processEvents()
msg.add_header('Content-Disposition', 'attachment', filename=filename)
message.attach(msg)
return {'raw': base64.urlsafe_b64encode(message.as_string())}
def SendMessage(service, user_id, message, size):
"""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())
QCoreApplication.processEvents()
return message
except errors.HttpError, error:
pass
I succeed to insert/send message with large file, pythons code.
The google api documentations is not friendly for developers, and the "/upload" issue is totally unclear and not well documented, and it confusing a lot of developers.
The final line do the magic :)
def insert_message(service, message):
try:
if message['sizeEstimate'] > 6000000:
insert_large_message(service, message)
else:
insert_small_message(service, message)
except:
print ('Error: ----type: %s, ----value: %s, ----traceback: %s ************' % (sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2]))
def insert_small_message(service, message):
body = {'raw': message['raw'],'labelIds':message['labelIds'],'internalDateSource':'dateHeader'}
message = service.users().messages().insert(userId='me',body=body).execute()
def insert_large_message(service, message):
b = io.BytesIO()
message_bytes = base64.urlsafe_b64decode(str(message['raw']))
b.write(message_bytes)
body = {'labelIds':message['labelIds'],'internalDateSource':'dateHeader'}
media_body = googleapiclient.http.MediaIoBaseUpload(b, mimetype='message/rfc822' )
print('load big data!')
message = service.users().messages().insert(userId='me',body=body,media_body=media_body).execute()
'g' is my authorized api context. The call method will invoke execute on the object. The important thing is the Media calls and using both the media_body and the body params. This causes the message to be inserted with the label INBOX, and it will allow at least a 24MB file.
I ended up with two copies because the read timeout was too short:
f fetch 8:9 (flags INTERNALDATE RFC822.SIZE)
* 8 FETCH (RFC822.SIZE 24000720 INTERNALDATE "19-Jul-2007 17:12:26 +0000" FLAGS (\Seen))
* 9 FETCH (RFC822.SIZE 24000720 INTERNALDATE "19-Jul-2007 17:12:26 +0000" FLAGS (\Seen))
Sample code:
import mailbox
import StringIO
import googleapiclient.http
f = 'my-mbox-file.mbox'
params = {}
params[ 'internalDateSource' ] = 'dateHeader'
for m in mailbox.mbox( f, create=False ):
message_string = m.as_string()
params[ 'body' ] = { 'labelIds': [ 'INBOX' ] }
if len(message_string) > 6000000:
s = StringIO.StringIO()
s.write( message_string )
params[ 'media_body' ] = googleapiclient.http.MediaIoBaseUpload(
s, mimetype='message/rfc822' )
else:
params['body']['raw'] = (
base64.urlsafe_b64encode( message_string ) )
g.call( g.auth.users().messages().insert, params )
try:
del params[ 'media_body' ]
except KeyError:
pass
You need to use the MEDIA /upload option for things that large. Then you can send emails up to the max Gmail allows. Docs for how to use /upload:
https://developers.google.com/gmail/api/v1/reference/users/messages/send
The 10MB limitation is not well documented.

Forward email with attachment

I want to resend email with attachments in Python. I have this code for sending email but how can I reference to attachment in another email?
Sending
def show_emails():
M.select()
typ, data = M.search(None, 'All')
for num in data[0].split():
typ, data = M.fetch(num, '(RFC822)')
parser = Parser()
email = parser.parsestr(data[0][1])
print "MESSAGE NUMBER %s" % (num)
print 'Raw Date:'
print email.get('Date')
print "From:"
print email.get('From')
print "Subject: "
print email.get('Subject')
And this code is for sending
msg = MIMEMultipart()
mfrom = 'from#abc.com'
mto = 'to#abc.com'
msg['Subject'] = 'test'
msg['From'] = mfrom
msg['To'] = mto
msg['Date'] = formatdate()
# Open the file to scan in binary mode
fp = open('/path/to/file', 'rb')
attachment = MIMEBase('application', 'octet-stream')
attachment.set_payload(fp.read())
encoders.encode_base64(attachment)
attachment.add_header('Content-Disposition', 'attachment; filename="filename"')
fp.close()
msg.attach(attachment)
I know I need to check if there is any attachment. And how can I reference to attachment and forward it?
if msg.is_multipart():
for part in msg.walk():
fileName = part.get_filename()
if bool(fileName):
print "Attachment: %s " % (decode_header(fileName)[0][0])
else:
print "No attachments"
You cannot just reference it: that's what RFCs 4467-9 were for, but those weren't implemented by many servers and at this point I think they're dead. You have to download the attachment and send it as if you were sending a local file.

Categories

Resources