{"id":46,"date":"2024-01-14T21:00:00","date_gmt":"2024-01-14T18:00:00","guid":{"rendered":"https:\/\/fsaeed.blog\/?p=46"},"modified":"2026-10-31T00:18:53","modified_gmt":"2026-10-30T21:18:53","slug":"python-outlook-save-attachments","status":"publish","type":"post","link":"https:\/\/fsaeed.blog\/index.php\/2024\/01\/14\/python-outlook-save-attachments\/","title":{"rendered":"Reading Outlook Emails and Saving Attachments with Python"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Hello, tech enthusiasts! \ud83d\udc4b<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Every morning, the same kind of email arrives: &#8220;Daily Sales Report&#8221;, with an Excel file attached. You open the email, click the file, choose <strong>Save As<\/strong>, find the right folder and click <strong>Save<\/strong>. It takes a minute. But a minute every day, plus the days you forget, adds up.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Today we will write a Python script that reads your Outlook Inbox, finds the right emails and saves the attachments for you. We will use <strong>pywin32<\/strong>. It is a library that lets Python talk to Windows programs like Outlook. Think of it like a remote control: Python presses the buttons inside Outlook for you.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">What you need<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Windows with the desktop Outlook app, already signed in to your email<\/li>\n\n\n<li>Python 3 from <a href=\"https:\/\/www.python.org\">https:\/\/www.python.org<\/a><\/li>\n\n\n<li>The pywin32 library<\/li>\n\n\n<li>A folder for the files, for example <code>C:\\Reports\\Attachments<\/code><\/li>\n\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Install pywin32 in the Command Prompt:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>pip install pywin32<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Note: this method works with the classic desktop Outlook only. It does not work with Outlook in the browser.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 1: Connect to Outlook and open the Inbox<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Create a file called <code>save_attachments.py<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import win32com.client\n\n# Connect to the Outlook app that is installed on this computer\noutlook = win32com.client.Dispatch(\"Outlook.Application\")\nnamespace = outlook.GetNamespace(\"MAPI\")\n\n# 6 is the number Outlook uses for the Inbox folder\ninbox = namespace.GetDefaultFolder(6)\nprint(\"Emails in Inbox:\", inbox.Items.Count)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Run it with <code>python save_attachments.py<\/code>. If you see a number, Python is talking to Outlook.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>MAPI<\/strong> is the system Outlook uses to reach your mailbox. You do not need to understand it, just use the name.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 2: Loop through the emails<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Now we look at each email. We sort them so the newest come first:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>items = inbox.Items\nitems.Sort(\"[ReceivedTime]\", True)   # True = newest first\n\nfor item in items:\n    # 43 means \"mail item\". Skip meeting invites and other items.\n    if item.Class != 43:\n        continue\n    print(item.ReceivedTime, \"|\", item.Subject)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Your Inbox can also hold calendar invites and delivery reports. They do not always have the same fields, so we skip anything that is not a normal email.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 3: The full script with filters<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Now let us filter by subject and date, and save the files. Replace the code with this full version:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import datetime\nimport re\nfrom pathlib import Path\n\nimport win32com.client\n\nSUBJECT_WORD = \"Daily Sales Report\"          # text to look for in the subject\nDAYS_BACK = 7                                # only check the last 7 days\nSAVE_FOLDER = Path(r\"C:\\Reports\\Attachments\")\n\nSAVE_FOLDER.mkdir(parents=True, exist_ok=True)\nsince = datetime.date.today() - datetime.timedelta(days=DAYS_BACK)\n\noutlook = win32com.client.Dispatch(\"Outlook.Application\")\ninbox = outlook.GetNamespace(\"MAPI\").GetDefaultFolder(6)\n\nitems = inbox.Items\nitems.Sort(\"[ReceivedTime]\", True)  # newest first\n\nsaved = 0\nfor item in items:\n    if item.Class != 43:             # skip non-email items\n        continue\n\n    received = item.ReceivedTime.date()\n    if received &lt; since:\n        break                        # sorted newest first, so we can stop here\n\n    if SUBJECT_WORD.lower() not in item.Subject.lower():\n        continue\n\n    for att in item.Attachments:\n        # Remove characters that Windows does not allow in file names\n        clean_name = re.sub(r'[\\\\\/:*?\"&lt;&gt;|]', \"_\", att.FileName)\n        # Add the date so files with the same name do not overwrite each other\n        target = SAVE_FOLDER \/ f\"{received:%Y-%m-%d}_{clean_name}\"\n        att.SaveAsFile(str(target))  # Outlook needs a full path as text\n        saved += 1\n        print(\"Saved:\", target)\n\nprint(f\"Done. {saved} file(s) saved.\")<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Step 4: Understand the important parts<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>ReceivedTime<\/code> gives the date and time the email arrived. We use <code>.date()<\/code> to compare only the day.<\/li>\n\n\n<li>Because the list is sorted newest first, we can <code>break<\/code> when we reach an old email. This makes the script fast, even with a big Inbox.<\/li>\n\n\n<li><code>att.SaveAsFile()<\/code> saves one attachment. It needs a full path, like <code>C:\\Reports\\Attachments\\file.xlsx<\/code>, as text. That is why we use <code>str(target)<\/code>.<\/li>\n\n\n<li>We add the date to the file name. Daily reports often have the same name every day, and we do not want to overwrite yesterday&#8217;s file.<\/li>\n\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Step 5: Run and check<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Run <code>python save_attachments.py<\/code>. Open <code>C:\\Reports\\Attachments<\/code> and check the files. \ud83c\udf89<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This script only reads emails and saves files. It does not delete or move anything in Outlook, so it is safe to run many times.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Common problems<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong><code>pywintypes.com_error<\/code> when connecting.<\/strong> \u2192 Open Outlook once by hand and make sure it is signed in. Also check you are using desktop Outlook.<\/li>\n\n\n<li><strong>Logos and signature images are saved too.<\/strong> \u2192 Skip small files: add <code>if att.Size &lt; 10000: continue<\/code>, or only keep names ending with <code>.xlsx<\/code>.<\/li>\n\n\n<li><strong>No emails found, but you can see them.<\/strong> \u2192 Maybe a rule moves them to another folder. Use <code>inbox.Folders[\"Reports\"]<\/code> to open a subfolder of the Inbox.<\/li>\n\n\n<li><strong>Outlook shows a security prompt.<\/strong> \u2192 This protects you from unknown programs. Keep your antivirus active and allow access only for scripts you wrote yourself.<\/li>\n\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Wrap-up<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">You now have a script that collects your daily files without a single click. Next, combine it with the pandas cleaning from an earlier post: save the attachment, clean it, and build your report, all in one run. Happy automating!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Use Python and pywin32 to find emails in your Outlook Inbox and save their attachments to a folder automatically.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[7,15,10,12,16],"class_list":["post-46","post","type-post","status-publish","format-standard","hentry","category-uncategorized","tag-automation","tag-email","tag-outlook","tag-python","tag-windows"],"_links":{"self":[{"href":"https:\/\/fsaeed.blog\/index.php\/wp-json\/wp\/v2\/posts\/46","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fsaeed.blog\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/fsaeed.blog\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/fsaeed.blog\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/fsaeed.blog\/index.php\/wp-json\/wp\/v2\/comments?post=46"}],"version-history":[{"count":1,"href":"https:\/\/fsaeed.blog\/index.php\/wp-json\/wp\/v2\/posts\/46\/revisions"}],"predecessor-version":[{"id":87,"href":"https:\/\/fsaeed.blog\/index.php\/wp-json\/wp\/v2\/posts\/46\/revisions\/87"}],"wp:attachment":[{"href":"https:\/\/fsaeed.blog\/index.php\/wp-json\/wp\/v2\/media?parent=46"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fsaeed.blog\/index.php\/wp-json\/wp\/v2\/categories?post=46"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fsaeed.blog\/index.php\/wp-json\/wp\/v2\/tags?post=46"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}