I'm trying to send an email with an attachment (ideally multiple attachments) that are larger than 10 MB and smaller than the limit of 25 MB in total. The reason I mention 10 MB is because it seems to be the lower bound for when the normal way of attaching files stops working and you get Error 10053.
I've read in the documentation that the best way to do this would be by using the resumable upload method but I haven't been able to get it to work nor have I been able to find any good examples in Python. Most of the SO questions on this simply link back to the documentation which doesn't have a Python example or their code resulted in other errors.
I'm looking for an explanation in Python because I want to make sure I understand it correctly.
Questions I've looked through:
Attaching a file using Resumable upload w/ Gmail API
Gmail Api resumable upload Rest( attachment larger than 5MB)
using /upload urls with Google API client
How to upload large messages to Gmail
Error 10053 When Sending Large Attachments using Gmail API
Sending email via gmail & python
MIMEMultipart, MIMEText, MIMEBase, and payloads for sending email with file attachment in Python
Code:
import base64
import json
import os
from email import utils, encoders
from email.message import EmailMessage
from email.mime import application, multipart, text, base, image, audio
import mimetypes
from apiclient import errors
from googleapiclient import discovery, http
from google.oauth2 import service_account
def send_email(email_subject, email_body, email_sender='my_service_account#gmail.com', email_to='', email_cc='', email_bcc='', files=None):
# Getting credentials
with open(os.environ.get('SERVICE_KEY_PASSWORD')) as f:
service_account_info = json.loads(f.read())
# Define which scopes we're trying to access
SCOPES = ['https://www.googleapis.com/auth/gmail.send']
# Setting up credentials using the gmail api
credentials = service_account.Credentials.from_service_account_info(service_account_info, scopes=SCOPES)
# This allows us to assign an alias account to the message so that the messages aren't coming from 'ServiceDriod-8328balh blah blah'
delegated_credentials = credentials.with_subject(email_sender)
# 'Building' the service instance using the credentials we've passed
service = discovery.build(serviceName='gmail', version='v1', credentials=delegated_credentials)
# Building out the email
message = multipart.MIMEMultipart()
message['to'] = email_to
message['from'] = email_sender
message['date'] = utils.formatdate(localtime=True)
message['subject'] = email_subject
message['cc'] = email_cc
message['bcc'] = email_bcc
message.attach(text.MIMEText(email_body, 'html'))
for f in files or []:
mimetype, encoding = mimetypes.guess_type(f)
# 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 an unrecognized extension we set mimetype to 'application/octet-stream' so it won't return None again.
if mimetype is None or encoding is not None:
mimetype = 'application/octet-stream'
main_type, sub_type = mimetype.split('/', 1)
# Creating the attachement:
# This part is used to tell how the file should be read and stored (r, or rb, etc.)
if main_type == 'text':
print('text')
with open(f, 'rb') as outfile:
attachement = text.MIMEText(outfile.read(), _subtype=sub_type)
elif main_type == 'image':
print('image')
with open(f, 'rb') as outfile:
attachement = image.MIMEImage(outfile.read(), _subtype=sub_type)
elif main_type == 'audio':
print('audio')
with open(f, 'rb') as outfile:
attachement = audio.MIMEAudio(outfile.read(), _subtype=sub_type)
elif main_type == 'application' and sub_type == 'pdf':
with open(f, 'rb') as outfile:
attachement = application.MIMEApplication(outfile.read(), _subtype=sub_type)
else:
attachement = base.MIMEBase(main_type, sub_type)
with open(f, 'rb') as outfile:
attachement.set_payload(outfile.read())
encoders.encode_base64(attachement)
attachement.add_header('Content-Disposition', 'attachment', filename=os.path.basename(f))
message.attach(attachement)
media_body = http.MediaFileUpload(files[0], chunksize=500, resumable=True)
print('Uploading large file...')
body = {'raw': base64.urlsafe_b64encode(message.as_bytes()).decode()}
message = (service.users().messages().send(userId='me', body=body, media_body=media_body).execute())
Note: Right now, in the MediaFileUpload I'm using files[0] because I'm only using one file for testing and I just wanted to attach one file for now until it works.
Error:
Exception has occurred: ResumableUploadError
<HttpError 400 "Bad Request">
File "C:\Users\CON01599\AppData\Local\Continuum\anaconda3\Lib\site-packages\googleapiclient\http.py", line 927, in next_chunk
raise ResumableUploadError(resp, content)
File "C:\Users\CON01599\AppData\Local\Continuum\anaconda3\Lib\site-packages\googleapiclient\_helpers.py", line 130, in positional_wrapper
return wrapped(*args, **kwargs)
File "C:\Users\CON01599\AppData\Local\Continuum\anaconda3\Lib\site-packages\googleapiclient\http.py", line 822, in execute
_, body = self.next_chunk(http=http, num_retries=num_retries)
File "C:\Users\CON01599\AppData\Local\Continuum\anaconda3\Lib\site-packages\googleapiclient\_helpers.py", line 130, in positional_wrapper
return wrapped(*args, **kwargs)
File "C:\Users\CON01599\Documents\GitHub\pipelines\components\email\send_email.py", line 105, in send_email
message = (service.users().messages().send(userId='me', body=body, media_body=media_body).execute())
Answer:
import base64
import io
import json
import os
from email import utils, encoders
from email.message import EmailMessage
from email.mime import application, multipart, text, base, image, audio
import mimetypes
from apiclient import errors
from googleapiclient import discovery, http
from google.oauth2 import service_account
def get_environment_variables():
""" Retrieves the environment variables and returns them in
a dictionary object.
"""
env_var_dict = {
'to': os.environ.get('TO'),
'subject': os.environ.get('SUBJECT'),
'body': os.environ.get('BODY'),
'file': os.environ.get('FILE')
}
return env_var_dict
def send_email(email_subject, email_body, email_sender='my_service_account#gmail.com', email_to='', email_cc='', email_bcc='', files=None):
# Pulling in the string value of the service key from the parameter
with open(os.environ.get('SERVICE_KEY_PASSWORD')) as f:
service_account_info = json.loads(f.read())
# Define which scopes we're trying to access
SCOPES = ['https://www.googleapis.com/auth/gmail.send']
# Setting up credentials using the gmail api
credentials = service_account.Credentials.from_service_account_info(service_account_info, scopes=SCOPES)
# This allows us to assign an alias account to the message so that the messages aren't coming from 'ServiceDriod-8328balh blah blah'
delegated_credentials = credentials.with_subject(email_sender)
# 'Building' the service instance using the credentials we've passed
service = discovery.build(serviceName='gmail', version='v1', credentials=delegated_credentials)
# Building out the email
message = multipart.MIMEMultipart()
message['to'] = email_to
message['from'] = email_sender
message['date'] = utils.formatdate(localtime=True)
message['subject'] = email_subject
message['cc'] = email_cc
message['bcc'] = email_bcc
message.attach(text.MIMEText(email_body, 'html'))
for f in files or []:
f = f.strip(' ')
mimetype, encoding = mimetypes.guess_type(f)
# 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 an unrecognized extension we set mimetype to 'application/octet-stream' so it won't return None again.
if mimetype is None or encoding is not None:
mimetype = 'application/octet-stream'
main_type, sub_type = mimetype.split('/', 1)
# Creating the attachement:
# This part is used to tell how the file should be read and stored (r, or rb, etc.)
if main_type == 'text':
print('text')
with open(f, 'rb') as outfile:
attachement = text.MIMEText(outfile.read(), _subtype=sub_type)
elif main_type == 'image':
print('image')
with open(f, 'rb') as outfile:
attachement = image.MIMEImage(outfile.read(), _subtype=sub_type)
elif main_type == 'audio':
print('audio')
with open(f, 'rb') as outfile:
attachement = audio.MIMEAudio(outfile.read(), _subtype=sub_type)
elif main_type == 'application' and sub_type == 'pdf':
with open(f, 'rb') as outfile:
attachement = application.MIMEApplication(outfile.read(), _subtype=sub_type)
else:
attachement = base.MIMEBase(main_type, sub_type)
with open(f, 'rb') as outfile:
attachement.set_payload(outfile.read())
encoders.encode_base64(attachement)
attachement.add_header('Content-Disposition', 'attachment', filename=os.path.basename(f))
message.attach(attachement)
media_body = http.MediaIoBaseUpload(io.BytesIO(message.as_bytes()), mimetype='message/rfc822', resumable=True)
body_metadata = {} # no thread, no labels in this example
try:
print('Uploading file...')
response = service.users().messages().send(userId='me', body=body_metadata, media_body=media_body).execute()
print(response)
except errors.HttpError as error:
print('An error occurred when sending the email:\n{}'.format(error))
if __name__ == '__main__':
env_var_dict = get_environment_variables()
print("Sending email...")
send_email(email_subject=env_var_dict['subject'],
email_body=env_var_dict['body'],
email_to=env_var_dict['to'],
files=env_var_dict['file'].split(','))
print("Email sent!")
The issue you're having here is that your MediaUpload is a single attachment.
Instead of uploading a single attachment as a resumable MediaUpload, you need to upload the entire RFC822 message as a resumable MediaUpload.
In other words:
import ...
...
from io import BytesIO
from googleapiclient.http import MediaIoBaseUpload
SCOPES = [ 'scopes' ]
creds = get_credentials_somehow()
gmail = get_authed_service_somehow()
msg = create_rfc822_message(headers, email_body)
to_attach = get_attachment_paths_from_dir('../reports/tps/memos/2019/04')
add_attachments(msg, to_attach)
media = MediaIoBaseUpload(BytesIO(msg.as_bytes()), mimetype='message/rfc822', resumable=True)
body_metadata = {} # no thread, no labels in this example
resp = gmail.users().messages().send(userId='me', body=body_metadata, media_body=media).execute()
print(resp)
# { "id": "some new id", "threadId": "some new thread id", "labelIds": ["SENT"]}
I pieced this together from your provided code, reviewing this GitHub issue and Google's Inbox-to-Gmail email importer, specificially this bit.
When sending replies to existing messages, you will almost certainly have some sort of metadata that you should provide to help Gmail keep track of your new response and the original conversation. Namely, instead of an empty body parameter, you would pass informative metadata such as
body_metadata = { 'labelIds': [
"your label id here",
"another label id" ],
'threadId': "some thread id you took from the message you're replying to"
}
Other good refs:
API Client's Gmail PyDoc
Actual code used
You mention the attachment being larger than 10Mb, but you don't mention it being smaller than 25Mb: there's a limitation to gmail that attachments can't be larger than 25Mb, so if this is your case, there's simply no way to get this done, as it is beyond gmail limitations.
The explanation can be found here.
Can you confirm that your attachment is not too large?
Related
I'm attempting to pull email attachments from outlook and store them in an s3 bucket in aws. This is one of my first python projects, and its proving to be very difficult for me and is probably very messy code.
# see https://github.com/AzureAD/microsoft-authentication-library-for-python/blob/dev/sample/device_flow_sample.py
# This authenticates for first time login.
# As long as you call acquire_token_silent before you invoke any graph APIs, the tokens will stay up to date.
# The refresh token is good for 90 days, and automatically updates. Once you login, the tokens will
# be updated and stored in the cache (and persisted to a file), and will stay alive more-or-less indefinitely
# (there are some things that can invalidate it on the server side).
from __future__ import with_statement
import io
import sys
import json
import logging
import os
import tarfile
import atexit
from wsgiref import headers
import requests
import msal
import boto3
import base64
from botocore.exceptions import ClientError
import codecs
print('Starting...')
# logging
logging.basicConfig(level=logging.DEBUG) # Enable DEBUG log for entire script
logging.getLogger("msal").setLevel(logging.INFO) # Optionally disable MSAL DEBUG logs
client=boto3.client('secretsmanager')
# config
config = dict(
authority = "https://login.microsoftonline.com/common",
client_id = '123456',
scope = ["Mail.ReadWrite"],
username = 'username',
cache_file = client.get_secret_value(SecretId="demo-ms-graph")['SecretBinary'],
endpoint = 'https://graph.microsoft.com/v1.0/me/mailFolders/inbox/messages?$expand=attachments&$search="hasAttachments:true"'
)
# cache
cache = msal.SerializableTokenCache()
if os.path.exists(config["cache_file"]):
with tarfile.open('token.cache.tar.gz', "w:gz") as tar:
tar.add(config["cache_file"])
bts = open('token.cache.tar.gz','rb').read()
print("Length before",len(bts))
#sec=client.update_secret(SecretId="demo-ms-graph", SecretBinary=bts)
sec=client.get_secret_value(SecretId="demo-ms-graph")['SecretBinary']
print("Length after",len(sec))
with tarfile.open(fileobj=io.BytesIO(sec), mode='r:gz') as t:
d=t.extractfile('token.cache')
#print file content
print("File content",str(d.read()))
with tarfile.open(fileobj=io.BytesIO(sec), mode='r:gz') as t:
d=t.extractfile('token.cache')
# app
app = msal.PublicClientApplication(
config["client_id"], authority=config["authority"],
token_cache=cache)
print('Connecting to app..')
# exists?
result = None
accounts = app.get_accounts()
if accounts:
logging.info("found accounts in the app")
for a in accounts:
print(a)
if a["username"] == config["username"]:
result = app.acquire_token_silent(config["scope"], account=a)
break
if result and "access_token" in result:
# Calling graph using the access token
graph_data = requests.get( # Use token to call downstream service
config["endpoint"],
headers={'Authorization': 'Bearer ' + result['access_token']},).json()
#print("Graph API call result: %s" % json.dumps(graph_data, indent=2))
else:
print(result.get("error"))
print(result.get("error_description"))
print(result.get("correlation_id")) # You may need this when reporting a bug
main = 'https://graph.microsoft.com/v1.0/me/mailFolders/inbox/messages?$expand=attachments&$search="hasAttachments:true"'
response = requests.get(main, headers={'Authorization': 'Bearer ' + result['access_token']})
if response.status_code != 200:
raise Exception(response.json())
response_json = response.json()
print('Starting upload...')
emails = response_json['value']
s3 = boto3.client('s3')
bucket ='demo-email-app'
for email in emails:
email_id = email['id']
subject = email['subject']
if email['hasAttachments']:
print(subject)
attachments = email['attachments']
for attachment in attachments:
name = attachment['name']
fileContent = json.dumps(email, indent=2)
s3.put_object(Bucket=bucket, Key=name.replace('.', '_') + '.json', Body=fileContent.encode('UTF-8'))
print('Upload Complete')
#download_email_attachments(email_id, headers)
print('All uploads complete')
My secret is stored as binary in secrets manager, and it seems to be able to pull the secret and prints it fine. I'm running into an error in the # exists? section with "AttributeError: 'NoneType' object has no attribute 'get'"
Am I approaching this in the wrong way? I have the token stored in secretsmanager, and am trying to retrieve it for msal to use and authenticate my user to the MS Graph API so I can pull attachments from the Outlook account and store those in an s3 bucket.
I'm trying to make a telegram bot that sends media from local storage and i got this error.
Also if there is a list with over 10 items on it and you try to send as a album does telegram automatically seperates them to different album to send it?
A request to the Telegram API was unsuccessful. Error code: 400. Description: Bad Request: wrong HTTP URL specified
import telebot
import glob
import os
from telebot.types import InputMediaPhoto, InputMediaVideo
bot = telebot.TeleBot("")
#bot.message_handler(commands=['test'])
def test(message):
id = message.chat.id
path = "./vid/*.mp4"
vid_media = []
#i might remove this i think this is not needed
for files in glob.glob(path):
print(files)
for i in os.listdir("./vid/"):
vid_media.append(InputMediaVideo(i))
bot.send_message(id, "Sending videos...")
for i in vid_media:
with open(i, 'rb') as f:
bot.send_media_group(id, vid_media)
bot.polling()
It can't use directly path to local file.
You have to send content as bytes:
with open(filename, 'rb') as fh: # open in `byte mode`
data = fh.read() # read bytes
media = InputMediaVideo(data) # put bytes
vid_media.append(media)
Full working code:
import os
import glob
import telebot
from telebot.types import InputMediaPhoto, InputMediaVideo
TOKEN = os.getenv('TELEGRAM_TOKEN')
#print('TOKEN:', TOKEN)
bot = telebot.TeleBot(TOKEN)
#bot.message_handler(commands=['test'])
def test(message):
chat_id = message.chat.id
path = "./vid/*.mp4"
vid_media = []
for filename in glob.glob(path):
print('read:', filename)
with open(filename, 'rb') as fh:
data = fh.read()
media = InputMediaVideo(data)
vid_media.append(media)
bot.send_message(chat_id, "Sending videos...")
bot.send_media_group(chat_id, vid_media)
bot.polling()
EDIT:
Different modules may have different functionalities.
This code uses module telebot (pyTelegramBotAPI) and it can't use local path
but it seems module telegram can use pathlib.Path with local path in its InputMediaVideo.
I'm trying to send a simple test email using the Gmail API but I keep getting the same error for every code sample I find.
My code at the moment is this:
def validationService():
SCOPES = ['https://mail.google.com/']
SERVICE_ACCOUNT_FILE = 'creds.json'
creds = service_account.Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=SCOPES)
service = build('gmail', 'v1', credentials=creds)
return service
def SendMessage(service, user_id, message):
try:
message = (service.users().messages().send(userId=user_id, body=message).execute())
print('Message Id:',message['id'])
return message
except errors.HttpError as error:
print('An error occurred:', error)
def CreateMessage(sender, to, subject, message_text):
message = MIMEText(message_text)
message['to'] = to
message['from'] = sender
message['subject'] = subject
return {'raw': base64.urlsafe_b64encode(message.as_string().encode()).decode()}
service = validationService()
email = CreateMessage("sendermail#gmail.com", "receiverrmail#gmail.com", "Test", "This is a test")
sent = SendMessage(service, "sendermail#gmail.com", email)
Returns
>>> An error occurred: <HttpError 400 when requesting https://www.googleapis.com/gmail/v1/users/me/messages/send?alt=json returned "Bad Request">
I also don't understand the difference between the "sender" parameter at CreateMessage and userId at SendMessage.
If serves a purpose, I'm using a Service Account credential
-
Thanks!
When using a service account with the Gmail API. You need to set domain-wide delegation, which allows the service account to impersonate any user in your G Suite domain. Therefore, you must have a G Suite Account to be able to use domain wide-delegation as the docs say:
If you have a G Suite domain—if you use G Suite, for example—an
administrator of the G Suite domain can authorize an application to
access user data on behalf of users in the G Suite domain.
So now, why do you need to impersonate a user(a real person)? it's due to the fact a service account is a bot(not a real person) that is used to server-to-server interactions making possible your app calls Google APIs and although the service account has a parameter called client_email, which has a structure like name#project-randomnumber.iam.gserviceaccount.com it's not a real email that belongs to a real person (kind of confusing I know).
Having said that, I made some changes to your code. First, I modified your validationService function in order to build the service using domain-wide delegation.
def validationService():
# Set the crendentials
credentials = service_account.Credentials.\
from_service_account_file(SERVICE_ACCOUNT_FILE, scopes= SCOPES)
# Delegate the credentials to the user you want to impersonate
delegated_credentials = credentials.with_subject(USER_EMAIL)
service = discovery.build('gmail', 'v1', credentials=delegated_credentials)
return service
In your SendMessage function is not necessarily to pass the user who is going to send the email. Using me is enough as the Users.messages: send Parameters state:
userId string The user's email address. The special value me can be
used to indicate the authenticated user.
def SendMessage(service, message):
message = service.users().messages().send(userId="me", body=message).execute()
return message
Your whole code at the end could look like this one:
from googleapiclient import discovery, errors
from oauth2client import file, client, tools
from google.oauth2 import service_account
from email.mime.text import MIMEText
import base64
SERVICE_ACCOUNT_FILE = 'service_account.json'
SCOPES = [' https://mail.google.com/']
# The user we want to "impersonate"
USER_EMAIL = "user#domain"
def validationService():
# Set the crendentials
credentials = service_account.Credentials.\
from_service_account_file(SERVICE_ACCOUNT_FILE, scopes= SCOPES)
# Delegate the credentials to the user you want to impersonate
delegated_credentials = credentials.with_subject(USER_EMAIL)
service = discovery.build('gmail', 'v1', credentials=delegated_credentials)
return service
def SendMessage(service, message):
message = service.users().messages().send(userId="me", body=message).execute()
return message
def CreateMessage(sender, to, subject, message_text):
message = MIMEText(message_text)
message['to'] = to
message['from'] = sender
message['subject'] = subject
return {'raw': base64.urlsafe_b64encode(message.as_string().encode()).decode()}
def main():
try:
service = validationService()
email = CreateMessage(USER_EMAIL, "receiverrmail#domain", "Test", "This is a test")
email_sent = SendMessage(service, email)
print('Message Id:', email_sent['id'])
except errors.HttpError as err:
print('\n---------------You have the following error-------------')
print(err)
print('---------------You have the following error-------------\n')
if __name__ == '__main__':
main()
Notice
You also need to allow your service account to access Google's API when using domain-wide delegation by setting the Managing API client access on your G Suite account.
You don't break your head... Just use email and smtplib libraries. Very simple and fun sending email through Python, if you ask me. I have given my coding below and you should develop your own code based on this. If it works, just let me know - I will be happy. here it is....
import email, smtplib, os, time, fnmatch
from datetime import date, timedelta
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
def sendMailTo(recepient_id, today):
login_id = 'myemailID#gmail.com'
login_pwd = "mypassword"
fro_ = login_id
s_name = 'smtp.gmail.com'
s_port = 587
fileList = []
fi_type = '*.pdf'
file_path = "C:\\Myfolder\\Subfolder"
srvr = smtplib.SMTP(s_name, s_port)
srvr.ehlo()
srvr.starttls()
srvr.ehlo()
srvr.login(login_id, login_pwd)
sub = "Your subject here"
# loading MIMEMultipart obj onto outer var
outer = MIMEMultipart('alternative')
outer["From"] = 'Your company/Personal Name'
outer["To"] = recepient_id
outer['Subject'] = sub
# storing only pdf files
fileList = fnmatch.filter(os.listdir(file_path), fi_type)
for fi in fileList:
fi_name = os.path.join(file_path, fi)
fp = open(fi_name, 'rb')
img = MIMEImage(fp.read(), _subtype='jpg')
fp.close()
img.add_header('Content-disposition', "attachment", filename = fi)
outer.attach(img)
#start sending email with attachment with original file name
srvr.sendmail(fro_, recepient_id, outer.as_string())
part = None
outer = None
srvr.quit()
I have received an email that has an excel file as an attachment.
I would
I would like to use python to do the following:
Access my gmail account when I provide my username and password
Search for the specific email according to the mail's subject
Download one (or all) attachment from this mail into a folder of my choosing
I have seen some guides but none of them work for me and I wonder if there is a specific known 3rd party lib that does the following.
I have used the following script I found:
import email, getpass, imaplib, os
detach_dir = '.' # directory where to save attachments (default: current)
user = input("Enter your GMail username:")
pwd = getpass.getpass("Enter your password: ")
# connecting to the gmail imap server
m = imaplib.IMAP4_SSL("imap.gmail.com")
m.login(user, pwd)
m.select("cs2043") # here you a can choose a mail box like INBOX instead
# use m.list() to get all the mailboxes
resp, items = m.search(None,
"ALL") # you could filter using the IMAP rules here
(check http://www.example-code.com/csharp/imap-search-critera.asp)
items = items[0].split() # getting the mails id
for emailid in items:
resp, data = m.fetch(emailid,
"(RFC822)") # fetching the mail, "`(RFC822)`" means
"get the whole stuff", but you can ask for headers only, etc
email_body = data[0][1] # getting the mail content
mail = email.message_from_string(email_body) # parsing the mail content
to get a mail object
# Check if any attachments at all
if mail.get_content_maintype() != 'multipart':
continue
print
"[" + mail["From"] + "] :" + mail["Subject"]
# we use walk to create a generator so we can iterate on the parts and
forget about the recursive headach
for part in mail.walk():
# multipart are just containers, so we skip them
if part.get_content_maintype() == 'multipart':
continue
# is this part an attachment ?
if part.get('Content-Disposition') is None:
continue
# filename = part.get_filename()
filename = mail["From"] + "_hw1answer"
att_path = os.path.join(detach_dir, filename)
# Check if its already there
if not os.path.isfile(att_path):
# finally write the stuff
fp = open(att_path, 'wb')
fp.write(part.get_payload(decode=True))
fp.close()
The problem with this script is that as soon as I enter my correct password I receive:
Traceback (most recent call last):
File "gmail_report_analysis.py", line 9, in <module>
m.login(user, pwd)
File "C:\Users\Amir\AppData\Local\Programs\Python\Python36\lib\imaplib.py",
line 593, in login
raise self.error(dat[-1])
imaplib.error: b'[AUTHENTICATIONFAILED] Invalid credentials (Failure)'
You can use the python API for gmail to automate actions on your gmail account.
enable gmail api on google developer console and download credentials.json
install the python library - pip install --upgrade google-api-python-client oauth2client
create your python code file -
from __future__ import print_function
from googleapiclient.discovery import build
from httplib2 import Http
from oauth2client import file, client, tools
# If modifying these scopes, delete the file token.json.
SCOPES = 'https://www.googleapis.com/auth/gmail.readonly'
def main():
"""Shows basic usage of the Gmail API.
Lists the user's Gmail labels.
"""
# 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.
store = file.Storage('token.json')
creds = store.get()
if not creds or creds.invalid:
flow = client.flow_from_clientsecrets('credentials.json', SCOPES)
creds = tools.run_flow(flow, store)
service = build('gmail', 'v1', http=creds.authorize(Http()))
# Call the Gmail API
results = service.users().labels().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()
The above code is just to login to your gmail account and pull the labels in your gmail mailbox. There are other actions also you can perform like search messages and download attachments. Please refer here for more information.
Can someone help me send an email from my exchange account and add attachments. SMTP doesnt work, I get immediate timeout issues. 0365 doesn't save a copy to my sent folder. The only other one I know is exchangelib
from exchangelib import Account, Credentials, Message, Mailbox, FileAttachment
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.utils import formatdate
from email import encoders
email = 'XXXXXXXX'
password = 'XXXXXXXX'
a = Account(email, credentials=Credentials(email, password), autodiscover=True)
dir_path = ('C:/Users/Istcrmt/Documents/Python/PythonforAnaconda3.5/')
excel_name = 'test.xlsx'
#attach an excel file:
for i in email_list.itertuples():
# if you want a copy in the 'Sent' folder
m = Message(
account=a
,folder=a.sent
,subject=(i.AGENCY_NAME + ' I made an email script.')
,body='All bodies are beautiful'
,to_recipients=[Mailbox(email_address=i.NEW_MAIL)])
#attach files
m.attachments.append(part)
# m.attach(cover_letter)
m.send_and_save()
This is how you send emails with an attachment with exchangelib:
from exchangelib import ServiceAccount, Configuration, Account, DELEGATE
from exchangelib import Message, Mailbox, FileAttachment
from config import cfg # load your credentials
def send_email(account, subject, body, recipients, attachments=None):
"""
Send an email.
Parameters
----------
account : Account object
subject : str
body : str
recipients : list of str
Each str is and email adress
attachments : list of tuples or None
(filename, binary contents)
Examples
--------
>>> send_email(account, 'Subject line', 'Hello!', ['info#example.com'])
"""
to_recipients = []
for recipient in recipients:
to_recipients.append(Mailbox(email_address=recipient))
# Create message
m = Message(account=account,
folder=account.sent,
subject=subject,
body=body,
to_recipients=to_recipients)
# attach files
for attachment_name, attachment_content in attachments or []:
file = FileAttachment(name=attachment_name, content=attachment_content)
m.attach(file)
m.send_and_save()
credentials = ServiceAccount(username=cfg['user'],
password=cfg['password'])
config = Configuration(server=cfg['server'], credentials=credentials)
account = Account(primary_smtp_address=cfg['smtp_address'], config=config,
autodiscover=False, access_type=DELEGATE)
# Read attachment
attachments = []
with open('filestorage/numbers-test-document.pdf', 'rb') as f:
content = f.read()
attachments.append(('whatever.pdf', content))
# Send email
send_email(account, 'Test 14:35', 'works', ['info#example.com'],
attachments=attachments)
Related: Read emails and download attachment from an Exchange Server