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.audio import MIMEAudio from email.mime.base import MIMEBase from email.mime.image import MIMEImage from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.mime.application import MIMEApplication import mimetypes import base64 class SendEmail: # If modifying these scopes, delete the file token.pickle. SCOPES = ['https://www.googleapis.com/auth/gmail.send'] def create_message(self, sender, to, subject, message_text): message = MIMEText(message_text) message['to'] = to message['from'] = sender message['subject'] = subject raw_message = base64.urlsafe_b64encode(message.as_string().encode("utf-8")) return { 'raw': raw_message.decode("utf-8") } def create_Message_with_attachment(self, sender, to, subject, message_text_plain, message_text_html, attached_file): """Create a message for an email. message_text: The text of the email message. attached_file: The path to the file to be attached. Returns: An object containing a base64url encoded email object. """ ##An email is composed of 3 part : #part 1: create the message container using a dictionary { to, from, subject } #part 2: attach the message_text with .attach() (could be plain and/or html) #part 3(optional): an attachment added with .attach() ## Part 1 message = MIMEMultipart() #when alternative: no attach, but only plain_text message['to'] = to message['from'] = sender message['subject'] = subject ## Part 2 (the message_text) # The order count: the first (html) will be use for email, the second will be attached (unless you comment it) message.attach(MIMEText(message_text_html, 'html')) message.attach(MIMEText(message_text_plain, 'plain')) ## Part 3 (attachment) # # to attach a text file you containing "test" you would do: # # message.attach(MIMEText("test", 'plain')) #-----About MimeTypes: # It tells gmail which application it should use to read the attachment (it acts like an extension for windows). # If you dont provide it, you just wont be able to read the attachment (eg. a text) within gmail. You'll have to download it to read it (windows will know how to read it with it's extension). #-----3.1 get MimeType of attachment #option 1: if you want to attach the same file just specify it’s mime types #option 2: if you want to attach any file use mimetypes.guess_type(attached_file) my_mimetype, encoding = mimetypes.guess_type(attached_file) # If the extension is not recognized it will return: (None, None) # If it's an .mp3, it will return: (audio/mp3, None) (None is for the encoding) #for unrecognized extension it set my_mimetypes to 'application/octet-stream' (so it won't return None again). if my_mimetype is None or encoding is not None: my_mimetype = 'application/octet-stream' main_type, sub_type = my_mimetype.split('/', 1)# split only at the first '/' # if my_mimetype is audio/mp3: main_type=audio sub_type=mp3 #-----3.2 creating the attachment #you don't really "attach" the file but you attach a variable that contains the "binary content" of the file you want to attach #option 1: use MIMEBase for all my_mimetype (cf below) - this is the easiest one to understand #option 2: use the specific MIME (ex for .mp3 = MIMEAudio) - it's a shorcut version of MIMEBase #this part is used to tell how the file should be read and stored (r, or rb, etc.) if main_type == 'text': print("text") temp = open(attached_file, 'r') # 'rb' will send this error: 'bytes' object has no attribute 'encode' attachment = MIMEText(temp.read(), _subtype=sub_type) temp.close() elif main_type == 'image': print("image") temp = open(attached_file, 'rb') attachment = MIMEImage(temp.read(), _subtype=sub_type) temp.close() elif main_type == 'audio': print("audio") temp = open(attached_file, 'rb') attachment = MIMEAudio(temp.read(), _subtype=sub_type) temp.close() elif main_type == 'application' and sub_type == 'pdf': temp = open(attached_file, 'rb') attachment = MIMEApplication(temp.read(), _subtype=sub_type) temp.close() else: attachment = MIMEBase(main_type, sub_type) temp = open(attached_file, 'rb') attachment.set_payload(temp.read()) temp.close() #-----3.3 encode the attachment, add a header and attach it to the message # encoders.encode_base64(attachment) #not needed (cf. randomfigure comment) #https://docs.python.org/3/library/email-examples.html filename = os.path.basename(attached_file) attachment.add_header('Content-Disposition', 'attachment', filename=filename) # name preview in email message.attach(attachment) ## Part 4 encode the message (the message should be in bytes) message_as_bytes = message.as_bytes() # the message should converted from string to bytes. message_as_base64 = base64.urlsafe_b64encode(message_as_bytes) #encode in base64 (printable letters coding) raw = message_as_base64.decode() # need to JSON serializable (no idea what does it means) return {'raw': raw} def send_message(self, 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 Exception as e: print('An error occurred: %s' % e) return None def __init__(self): """Shows basic usage of the Gmail API. Lists the user's Gmail labels. """ 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('GmailApi/token.pickle'): with open('GmailApi/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( 'GmailApi/credentials.json', SendEmail.SCOPES) creds = flow.run_local_server(port=0) # Save the credentials for the next run with open('GmailApi/token.pickle', 'wb') as token: pickle.dump(creds, token) self.service = build('gmail', 'v1', credentials=creds, cache_discovery=False) def send(self, pdfFilePath, subject): msg = self.create_Message_with_attachment('alvinx.tan@gmail.com', 'experiment2020.ntu@gmail.com', subject, '', '', pdfFilePath) self.send_message(self.service, 'alvinx.tan@gmail.com', msg)