11

I have multiple emails that contain an attachment. I would like to download the attachment for unread emails and with a specific subject line.

for example, I got an email that has a subject "EXAMPLE" and contains an attachment. So how it would be Below code, I tried but it is not working" it's a Python code

#Subject line can be "EXAMPLE" 
      for subject_line in lst_subject_line:    
             # typ, msgs = conn.search(None,'(UNSEEN SUBJECT "' + subject_line + '")')
             typ, msgs = conn.search(None,'("UNSEEN")')
             msgs = msgs[0].split()
             print(msgs)
             outputdir = "C:/Private/Python/Python/Source/Mail Reader"
             for email_id in msgs:
                    download_attachments_in_email(conn, email_id, outputdir)

Thank You

Karthik S
  • 111
  • 1
  • 1
  • 4

4 Answers4

25

Most answers I could find were outdated.
Here's a python (>=3.6) script to download attachments from a Gmail account.
Make sure to check the filter options at the bottom and enable less secure apps on your google account.

import os
from imbox import Imbox # pip install imbox
import traceback

# enable less secure apps on your google account
# https://myaccount.google.com/lesssecureapps

host = "imap.gmail.com"
username = "username"
password = 'password'
download_folder = "/path/to/download/folder"

if not os.path.isdir(download_folder):
    os.makedirs(download_folder, exist_ok=True)
    
mail = Imbox(host, username=username, password=password, ssl=True, ssl_context=None, starttls=False)
messages = mail.messages() # defaults to inbox

for (uid, message) in messages:
    mail.mark_seen(uid) # optional, mark message as read

    for idx, attachment in enumerate(message.attachments):
        try:
            att_fn = attachment.get('filename')
            download_path = f"{download_folder}/{att_fn}"
            print(download_path)
            with open(download_path, "wb") as fp:
                fp.write(attachment.get('content').read())
        except:
            print(traceback.print_exc())

mail.logout()


"""
Available Message filters: 

# Gets all messages from the inbox
messages = mail.messages()

# Unread messages
messages = mail.messages(unread=True)

# Flagged messages
messages = mail.messages(flagged=True)

# Un-flagged messages
messages = mail.messages(unflagged=True)

# Messages sent FROM
messages = mail.messages(sent_from='sender@example.org')

# Messages sent TO
messages = mail.messages(sent_to='receiver@example.org')

# Messages received before specific date
messages = mail.messages(date__lt=datetime.date(2018, 7, 31))

# Messages received after specific date
messages = mail.messages(date__gt=datetime.date(2018, 7, 30))

# Messages received on a specific date
messages = mail.messages(date__on=datetime.date(2018, 7, 30))

# Messages whose subjects contain a string
messages = mail.messages(subject='Christmas')

# Messages from a specific folder
messages = mail.messages(folder='Social')
"""

For Self Sign Certificates use:

...
import ssl
    
context = ssl._create_unverified_context()
mail = Imbox(host, username=username, password=password, ssl=True, ssl_context=context, starttls=False)
...

Note:

Less secure apps & your Google Account

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.

Important: This deadline does not apply to Google Workspace or Google Cloud Identity customers. The enforcement date for these customers will be announced on the Workspace blog at a later date.

SRC


UPDATE 2022/08/22: You should be able to create an App Password to get around the "less secure apps" functionality being gone. (The latter still works in my business account, but had to create an App Password for my consumer account.) Using imaplib, I am able to login with an App Password.

Pedro Lobito
  • 94,083
  • 31
  • 258
  • 268
  • It's failing at `download_path = f"{download_folder}/{att_fn}"` with invalid syntax error – Arihant Godha Jun 27 '20 at 14:16
  • [f strings](https://realpython.com/python-f-strings/) are only available on python >= 3.6, for older versions you can use : `download_path = download_folder+"/"+att_fn`, or `download_path = "{}/{}".format(download_folder, att_fn)`, among [other options](https://pyformat.info/) – Pedro Lobito Jun 27 '20 at 15:42
  • I am getting this error `TimeoutError: [WinError 10060]`, how to get rid of this? – Ashu007 Feb 17 '21 at 10:58
  • 1
    You may have been blocked by google servers. – Pedro Lobito Feb 17 '21 at 13:30
  • Is there any way to fix this error? or is there any setting change required in my google account? – Ashu007 Feb 18 '21 at 07:14
  • Now it works just by turn on the less secure apps feature on google account. – Ashu007 Feb 18 '21 at 07:52
  • @PedroLobito hi buddy, can you please suggest how to download particular attachment out of multiple attached files in a single mail? – Ashu007 Feb 18 '21 at 11:16
  • Your can filter the attachments by extension. `att_fn = attachment.get('filename')` ... place your filter after this. – Pedro Lobito Feb 18 '21 at 11:56
3
from imap_tools import MailBox

# get all attachments from INBOX and save them to files
with MailBox('imap.my.ru').login('acc', 'pwd', 'INBOX') as mailbox:
    for msg in mailbox.fetch():
        for att in msg.attachments:
            print(att.filename, att.content_type)
            with open('C:/1/{}'.format(att.filename), 'wb') as f:
                f.write(att.payload)

https://github.com/ikvk/imap_tools

Vladimir
  • 6,162
  • 2
  • 32
  • 36
2

I found this most effective in my case. Just keep your outlook open while running the program and it will extract unread messages with specific subject line.

import datetime
import os
import win32com.client


path = os.path.expanduser("~/Documents/xyz/folder_to_be_saved")  #location o file today = datetime.date.today()  # current date if you want current

outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")  #opens outlook
inbox = outlook.GetDefaultFolder(6) 
messages = inbox.Items


def saveattachemnts(subject):
    for message in messages:
        if message.Subject == subject and message.Unread:
        #if message.Unread:  #I usually use this because the subject line contains time and it varies over time. So I just use unread

            # body_content = message.body
            attachments = message.Attachments
            attachment = attachments.Item(1)
            for attachment in message.Attachments:
                attachment.SaveAsFile(os.path.join(path, str(attachment)))
                if message.Subject == subject and message.Unread:
                    message.Unread = False
                break                
                
saveattachemnts('EXAMPLE 1')
saveattachemnts('EXAMPLE 2')
Aps
  • 214
  • 2
  • 9
1

I use that solution to get attachment from mailbox. It's up to you, download it or save to the local variable. As well please notice, I read all of the messages first, because of box being empty most of the time:

import imaplib
import email


class MailBox:
    SMTP_SERVER = 'imap.gmail.com'
    SMTP_PORT = 993
    USER = '<user_email>'
    PASSWORD = '<password>'

    def __init__(self):
        self.imap = imaplib.IMAP4_SSL(host=self.SMTP_SERVER, port=self.SMTP_PORT)
        self.imap.login(self.USER, self.PASSWORD)

    def __enter__(self):
        self.emails = self._get_all_messages()

    def __exit__(self, exc_type, exc_value, exc_traceback):
        self.imap.close()
        self.imap.logout()

    def fetch_message(self, num=-1):
        _, data = self.imap.fetch(self.emails[num], '(RFC822)')
        _, bytes_data = data[0]
        email_message = email.message_from_bytes(bytes_data)
        return email_message

    def get_attachment(self, num=-1):
        for part in self.fetch_message(num).walk():
            if part.get_content_maintype() == 'multipart' or part.get('Content-Disposition') is None:
                continue
            if part.get_filename():
                return part.get_payload(decode=True).decode('utf-8').strip()

    def _get_all_messages(self):
        self.imap.select('Inbox')
        status, data = self.imap.search(None, 'ALL')
        return data[0].split()
Vova
  • 3,117
  • 2
  • 15
  • 23