Quick Answer
You can’t download Outlook email attachments using Python directly from the Outlook desktop client — but you can, reliably, by connecting to Outlook via COM (Windows only) or switching to Microsoft Graph API (cross-platform). For most internal corporate users on Windows, use pywin32 with Outlook’s COM interface: File > Options > Trust Center > Trust Center Settings > Macro Settings > Enable all macros (temporarily), then run Python code that accesses Outlook.Application and iteratesMailItem.Attachments. It works with delegate mailboxes and shared mailboxes if your account has Full Access permissions.
Step-by-Step Walkthrough
- Install pywin32: Run
pip install pywin32in Command Prompt (Admin). Then runpython Scripts/pywin32_postinstall.py -installfrom your Python Scripts folder — this registers COM objects. Skip this, and your script will fail silently. - Launch Outlook first: The COM interface requires Outlook.exe to be running. No background service. If Outlook isn’t open,
win32com.client.Dispatch('Outlook.Application')raises an exception. Start Outlook manually — even minimized — before running your script. - Identify the target folder: Use the Outlook Object Model path:
Namespace.GetDefaultFolder(6)for Inbox (6 = olFolderInbox), or navigate deeper withGetSharedDefaultFolderfor shared mailboxes. Example for shared mailbox ‘procurement@company.com’:Namespace.CreateRecipient('procurement@company.com').Resolve(), thenNamespace.GetSharedDefaultFolder(recipient, 6). - Loop & save attachments: For each
MailItem, iterateitem.Attachments. Use.SaveAsFile(os.path.join(save_dir, att.FileName)). Avoidatt.DisplayName— it’s unreliable. Stick toatt.FileName. Also: skip zero-byte files (att.Size == 0) — these are placeholders for linked content. - Add error handling: Wrap attachment saves in try/except. Some filenames contain illegal characters (e.g.,
/ \ : * ? " < > |). Sanitize withre.sub(r'[\\/:*?"<>|]', '_', att.FileName). Also catchpywintypes.com_error— common when an attachment is embedded OLE (like Excel charts) and not a standalone file.
Common Pitfalls
- You’re trying this on macOS or Linux.
pywin32only works on Windows. Outlook for Mac doesn’t expose COM. Use Microsoft Graph API instead — but that requires Azure app registration and token auth, not just local script access. - You’re running the script while Outlook is in Cached Exchange Mode and the target folder hasn’t synced locally. The COM interface reads from the local OST, not the server. Force sync: right-click the folder > Update Folder, or call
MAPIFolder.SyncObject.Sync()in code. - You’ve enabled Outlook security warnings (e.g., “A program is trying to access…”). These pop up per attachment save and block automation. Disable temporarily via Group Policy: Computer Configuration > Administrative Templates > Microsoft Outlook > Security > “Warn me when a program tries to send email on my behalf” → Disabled.
- You assume all attachments appear in
MailItem.Attachments. They don’t. Embedded images in HTML bodies show up inMailItem.HTMLBodyascid:references — not in the Attachments collection. Those require parsing MIME or using Graph API’smessage/attachmentsendpoint with$expand=attachments.
Pro Tips
The beauty of this approach is how cleanly it handles delegate access. If Sarah Chen (VP Marketing) granted you ‘Owner’ rights to her mailbox, your script sees her Inbox as a separate store — no extra auth needed. Just use Namespace.Stores.Item('Sarah Chen').GetRootFolder().Folders.Item('Inbox').
What most people don’t realize is that Outlook caches attachment metadata — including size and extension — even before downloading. So you can filter aggressively before saving: if att.Type == 5 and att.Size > 10240 and '.pdf' in att.FileName.lower(): (Type 5 = olByValue, i.e., real file; Type 6 = olEmbeddedItem, i.e., OLE object).
Here’s the counterintuitive one: Don’t use Attachments.Item(i) with index loops. It’s unstable across Outlook versions. Always iterate with for att in item.Attachments:. Index-based access fails silently in Outlook 2016 when attachments are added mid-loop — yes, really.
Troubleshooting
If your script runs but downloads zero files, check three things first:
- Outlook version: Outlook 365 (monthly channel) supports full COM access. Outlook 2016 LTSC does too — but Outlook for Microsoft 365 (web-installed) sometimes restricts COM if installed via Click-to-Run with enterprise policies. Run
outlook.exe /safeto test in Safe Mode — if it works there, an add-in is blocking COM. - Admin restrictions: Your IT team may have disabled COM programmability via registry key
HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Office\16.0\Outlook\Security\DisableCOMAddinsset to 1. Only admins can change this. - Shared mailbox timing:
GetSharedDefaultFolderreturnsNoneif the recipient isn’t resolved. Add a 500ms sleep afterrecipient.Resolve()— especially in Outlook 365 v2308+. Race condition confirmed in build 16.0.16924.20128.
For non-Windows users or strict security environments, Graph API is the fallback — but it’s slower and requires OAuth2 consent. You’ll need https://graph.microsoft.com/Mail.Read scope, and messages must be retrieved with ?$expand=attachments to include attachment data. Unlike COM, Graph gives you base64-encoded content — so you decode and write bytes manually.
| Your Situation | Best Method | Why |
|---|---|---|
| Windows, local admin, internal Exchange | pywin32 + COM | Fastest, no tokens, works with delegates/shared mailboxes out of the box |
| Mac/Linux or MFA-enforced tenant | Microsoft Graph API | Only cross-platform, MFA-compatible option — but needs Azure app reg and user consent |
| Need embedded images (e.g., email signatures) | Graph API + HTML parsing | COM ignores cid: references; Graph returns raw HTML + attachment list separately |
| Running unattended (e.g., scheduled task) | pywin32 + Outlook running as logged-in user | COM won’t work under SYSTEM or headless sessions — Outlook must run interactively |
| Large attachments (>10 MB) | Graph API with chunked upload | COM hangs or crashes; Graph supports resumable uploads via createUploadSession |
| Testing on noreply@company.com | Use Graph — not COM | COM requires interactive login; noreply accounts lack credentials for Outlook auth |
| IT blocks external Python tools | PowerShell + Outlook Interop | Same COM layer, but PowerShell is often whitelisted where Python isn’t |