Skip to content

Repository files navigation

Outlook Email Scanner for macOS

Python 3.10+ License: MIT macOS

Export Outlook emails as markdown and JSON without Microsoft Graph API or admin approval. Scan, search, and filter your Mac Outlook desktop emails using the macOS Accessibility API -- no OAuth, no API keys, no IT ticket required.

Built for developers, data scientists, and power users who need their Outlook emails accessible to AI tools like Claude Code, Cursor, Copilot, or any LLM that reads local files.

Why This Exists

Microsoft Graph API requires tenant admin consent, OAuth setup, and weeks of IT approval. This tool bypasses all of that by reading Outlook's UI directly through the macOS Accessibility tree. It's ~500x faster than screenshot-based automation and costs nothing.

Microsoft Graph API Outlook Scanner
Admin approval Required Not needed
Setup time Days to weeks 2 minutes
OAuth / API keys Required None
Rate limits Yes None
Works offline No Yes
Data leaves machine Yes Never

Features

Free Tier

  • Scan up to 30 emails per run
  • Single-account scanning
  • Markdown output -- clean, structured files with sender, recipients, date, body
  • Smart deduplication -- persistent across runs, never re-scans the same email
  • Claude Code skill -- say "check my inbox" and Claude reads your emails
  • 100% local and private -- emails never leave your machine

Pro Tier

  • Scan up to 500 emails per run
  • Multi-account auto-discovery -- detects all Outlook accounts automatically
  • Multi-folder scanning -- Inbox, Sent Items, Drafts, or any custom folder
  • Keyword search -- search your Outlook via the scanner, save results as markdown + JSON
  • Date range filtering -- scan emails from specific time periods
  • Sender and domain filtering -- include/exclude specific senders or domains
  • Unread-only mode -- scan only unread emails
  • JSON output -- structured JSON alongside markdown for programmatic use
  • Scan manifests -- metadata files tracking every scan run
  • Persistent scan logging -- full scan history at ~/.outlook-scanner/scan_log.json
  • Attachment detection -- identifies attached files with names and types
  • YAML configuration -- customize behavior via ~/.outlook-scanner/config.yaml

Requirements

  • macOS 14+ (Sonoma or later)
  • Microsoft Outlook for Mac (version 16.x)
  • Python 3.10+
  • Accessibility permissions for your terminal app

Quick Start

1. Install

git clone https://gitlab.com/timo2026/outlook-email-scanner.git
cd outlook-email-scanner
pip install -e .

2. Grant Accessibility Permission

System Settings > Privacy & Security > Accessibility -- add your terminal app (Terminal, iTerm2, Warp) or AI coding tool (Cursor, VS Code).

This single permission covers both reading Outlook's UI tree and mouse/keyboard control for scrolling and account switching.

3. Scan Your Inbox

from src.scanner import scan_outlook

stats = scan_outlook()
print(stats)
# {'found': 15, 'processed': 10, 'skipped': 5, 'errors': 0}

Emails are saved to ~/Desktop/outlook-emails/default/ as markdown files.

Usage

Basic Scan (Free)

from src.scanner import scan_outlook

# Scan the currently active folder
stats = scan_outlook()

Search Emails (Pro)

from src.scanner import scan_outlook

# Search for specific emails, save matching results
stats = scan_outlook(search_query="quarterly report")

Scan Multiple Folders (Pro)

from src.scanner import scan_outlook

# Scan Inbox, Sent Items, and Drafts
stats = scan_outlook(
    account="work",
    folders=["Inbox", "Sent Items", "Drafts"],
)

# Per-folder results available in stats["folders"]
for folder, folder_stats in stats["folders"].items():
    print(f"{folder}: {folder_stats}")

Auto-Discover and Scan All Accounts (Pro)

from src.scanner import scan_all_outlook_accounts

# Auto-discovers all accounts from Outlook sidebar
results = scan_all_outlook_accounts()

for account, stats in results.items():
    print(f"{account}: {stats}")

Discover Available Accounts

from src.scanner import discover_accounts

accounts = discover_accounts()
print(accounts)
# ['alice@company.com', 'bob@gmail.com']

Scan with Filters (Pro)

from datetime import datetime
from src.scanner import scan_outlook
from src.controller import OutlookController
from src.reader import OutlookReader

reader = OutlookReader()
reader.connect()

controller = OutlookController(
    reader,
    account="work",
    unread_only=True,                          # Only unread emails
    since=datetime(2026, 3, 1),                # From March 1st
    until=datetime(2026, 3, 31),               # To March 31st
    include_domains=["company.com"],            # Only from company.com
    exclude_senders=["noreply@company.com"],    # Skip automated emails
)

stats = controller.run()
print(stats)
# {'found': 50, 'processed': 12, 'skipped': 3, 'filtered': 35, 'errors': 0}

Command Line

# Quick scan
python -c "from src.scanner import scan_outlook; print(scan_outlook())"

# Search scan
python -c "from src.scanner import scan_outlook; print(scan_outlook(search_query='invoice'))"

Use with AI Tools

Claude Code

This repo includes a SKILL.md file that works as a Claude Code skill. Install it and say "check my inbox":

mkdir -p ~/.claude/skills/outlook-email-scan
cp SKILL.md ~/.claude/skills/outlook-email-scan/SKILL.md

Cursor / Copilot / Any LLM

The scanner saves emails as plain markdown files to ~/Desktop/outlook-emails/. Point any AI tool at that directory to read, search, summarize, or analyze your emails.

Output Format

Markdown

Each email is saved as YYYY-MM-DD-Subject.md:

# Meeting Tomorrow

**From:** Alice Smith <alice@company.com>
**To:** Team <team@company.com>
**Date:** 2026-03-18 09:30

## Body

Hi team,

Just a reminder about our meeting tomorrow at 2 PM.

Best,
Alice

JSON (Pro)

Each email also saved as YYYY-MM-DD-Subject.json for programmatic access:

{
  "subject": "Meeting Tomorrow",
  "sender": "Alice Smith <alice@company.com>",
  "recipients": "Team <team@company.com>",
  "timestamp": "2026-03-18 09:30",
  "body": "Hi team...",
  "account": "work",
  "folder": "Inbox",
  "saved_at": "2026-03-18T10:00:00Z",
  "has_attachments": false
}

Scan manifests (scan-{date}.json) are also generated with metadata for each scan run.

Configuration

Create ~/.outlook-scanner/config.yaml to customize behavior:

# Timing (seconds)
screenshot_wait: 1.5
scroll_wait: 1.0
scan_timeout: 900

# Limits (overridden by Pro license)
max_emails: 30
max_scrolls: 5

# Output
output_dir: ~/Desktop/outlook-emails

# Folders to skip when scanning all folders
blacklisted_folders:
  - Archive
  - Junk Email
  - Deleted Items
  - Trash
  - Spam

Architecture

The scanner uses a modular architecture with 8 components:

Module Purpose
scanner.py High-level entry point. Multi-account and multi-folder orchestration
controller.py Scan engine. Iterates emails, applies filters, saves output, logs stats
reader.py Low-level Accessibility API interface via atomacos
config.py YAML-based configuration with license-tier awareness
filters.py Date range, sender, and domain filtering
folders.py Folder navigation and blacklist management
dedup.py Persistent hash-based deduplication across runs
structured.py JSON output and scan manifest generation
license.py Pro license activation and validation

Accessibility Tree Structure (Outlook 16.x)

Window ("Inbox - user@company.com")
  +-- SplitGroup (main)
        +-- SplitGroup (inner)
              +-- SplitGroup (content)
                    |-- Group [0]      -> message list
                    |     +-- ScrollArea -> Table -> Rows
                    |-- Splitter [1]
                    |-- Group [2]      -> email headers (subject, from, to)
                    |-- ScrollArea [3]
                    +-- Group [4]      -> email body
                          +-- ... -> WebArea -> StaticText elements

Data Storage

All scanner data lives in ~/.outlook-scanner/:

File Purpose
config.yaml User configuration overrides
seen_hashes.json Deduplication hashes (persistent across runs)
scan_log.json Scan history (last 1000 runs)
license.json Pro license token

Email output goes to ~/Desktop/outlook-emails/<account>/.

Limitations

  • macOS only -- uses the macOS Accessibility API
  • Outlook for Mac only -- tested with Outlook 16.x
  • Requires Outlook to be open -- this is desktop automation, not an API
  • Reading pane must be visible -- the scanner reads from the reading pane
  • No attachment download -- detects attachments but doesn't download them (planned)

Tests

pip install -e ".[dev]"
pytest

All tests mock the Accessibility API so they run without Outlook.

License

MIT -- see LICENSE for details.

Built by Timo Labs.

About

Scan Mac Outlook desktop emails via macOS Accessibility API and save as markdown. No admin permissions, no OAuth, no API keys required.

Topics

Resources

Contributing

Stars

9 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages