<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Joseph Solomon</title>
    <description>The latest articles on DEV Community by Joseph Solomon (@joseph_solomon_20a1569494).</description>
    <link>https://dev.to/joseph_solomon_20a1569494</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4008392%2F90bfc37f-d6ea-4c20-9ef0-660f423b343a.jpg</url>
      <title>DEV Community: Joseph Solomon</title>
      <link>https://dev.to/joseph_solomon_20a1569494</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/joseph_solomon_20a1569494"/>
    <language>en</language>
    <item>
      <title>Claude Code to Outlook via pywin32 — no MCP, no permission, no problem.</title>
      <dc:creator>Joseph Solomon</dc:creator>
      <pubDate>Mon, 29 Jun 2026 15:08:22 +0000</pubDate>
      <link>https://dev.to/joseph_solomon_20a1569494/claude-code-to-outlook-via-pywin32-no-mcp-no-permission-no-problem-113b</link>
      <guid>https://dev.to/joseph_solomon_20a1569494/claude-code-to-outlook-via-pywin32-no-mcp-no-permission-no-problem-113b</guid>
      <description>&lt;p&gt;I work on a locked-down enterprise Windows machine. Can't register an Azure app. Can't install browser extensions. Managed device — IT controls what's approved.&lt;/p&gt;

&lt;p&gt;I still needed Claude Code to read and draft my emails.&lt;/p&gt;

&lt;p&gt;The fix turned out to be simpler than I expected.&lt;/p&gt;




&lt;h3&gt;
  
  
  What COM automation actually is
&lt;/h3&gt;

&lt;p&gt;Outlook Desktop on Windows exposes a COM interface. It's been there since the 90s. Any program running on the same machine can connect to it, provided Outlook is open and signed in.&lt;/p&gt;

&lt;p&gt;That's the whole authentication model: if Outlook is running, you're in.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pywin32&lt;/code&gt; wraps this interface in Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;win32com.client&lt;/span&gt;
&lt;span class="n"&gt;outlook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;win32com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Outlook.Application&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;inbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;outlook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GetNamespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MAPI&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nc"&gt;GetDefaultFolder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One import. One dispatch call. You're talking to Outlook and you have access to almost anything you can do with a mouse and keyboard.&lt;/p&gt;




&lt;h3&gt;
  
  
  The bridge pattern
&lt;/h3&gt;

&lt;p&gt;Claude wrote a single Python file — &lt;code&gt;outlook_bridge.py&lt;/code&gt; — that exposes Outlook commands as CLI subcommands and returns JSON.&lt;/p&gt;

&lt;p&gt;Claude Code calls it via Bash:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python outlook_bridge.py list &lt;span class="nt"&gt;--count&lt;/span&gt; 10 &lt;span class="nt"&gt;--pretty&lt;/span&gt;
python outlook_bridge.py search &lt;span class="s2"&gt;"renewal"&lt;/span&gt; &lt;span class="nt"&gt;--folder&lt;/span&gt; inbox
python outlook_bridge.py reply &amp;lt;entry_id&amp;gt; &lt;span class="nt"&gt;--body&lt;/span&gt; &lt;span class="s2"&gt;"Noted, will revert."&lt;/span&gt; &lt;span class="nt"&gt;--draft&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Subject"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Q2 renewal — action required"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"SenderName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Alice Tan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ReceivedTime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-06-12T09:14:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Unread"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"BodyPreview"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Following up on the renewal terms we discussed..."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude processes the JSON, reasons over it, and calls the next command in the chain. Read a thread for context → draft a reply → hand it back for review. All without leaving the Claude Code conversation.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why not MCP?
&lt;/h3&gt;

&lt;p&gt;I considered it. MCP is worth it when you need a long-running server process and cross-client compatibility.&lt;/p&gt;

&lt;p&gt;For this use case, it wasn't worth it — for three reasons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;COM object lifetime.&lt;/strong&gt; A long-running MCP server has to maintain the COM connection across calls, handle Outlook restarts, and manage session state. A direct Bash call has none of that: Claude shells out, Python grabs the running Outlook process, JSON comes back, process exits cleanly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rate limits.&lt;/strong&gt; MCP servers that wrap an API (like Microsoft Graph) inherit that API's throttling. Broad-spectrum queries — pulling a large folder, searching across multiple mailboxes — hit those limits fast. The bridge talks directly to the local COM interface. No API in the middle, no rate limit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Token efficiency.&lt;/strong&gt; The bridge returns exactly what I define: a JSON object with the fields I want. MCP tool responses carry protocol overhead on top of the payload. For high-frequency operations inside a single conversation, that adds up.&lt;/p&gt;

&lt;p&gt;One Python file. No daemon. No protocol overhead.&lt;/p&gt;

&lt;p&gt;If the constraints above don't apply to your setup, an MCP server is a reasonable alternative. For a local single-user workflow on Windows, this is the shorter path.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why not Microsoft Graph?
&lt;/h3&gt;

&lt;p&gt;Graph works cross-platform and headless. The trade-off: Azure app registration, OAuth consent flow, ongoing token refresh. On a managed enterprise device, that registration process may not be available to you.&lt;/p&gt;

&lt;p&gt;COM requires none of it. You're riding the session that's already open on your machine.&lt;/p&gt;




&lt;h3&gt;
  
  
  The permission setup
&lt;/h3&gt;

&lt;p&gt;One entry in &lt;code&gt;.claude/settings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"allow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Bash(python *outlook_bridge.py*:*)"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude can call the bridge and nothing else. No open-ended Bash access required.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;send&lt;/code&gt;, &lt;code&gt;reply&lt;/code&gt;, and &lt;code&gt;forward&lt;/code&gt; all work. My default is &lt;code&gt;--draft&lt;/code&gt; — Claude saves to Drafts and I review before sending. Accidental sends are still a real risk when you're chaining operations inside a conversation. The CLAUDE_INTEGRATION.md in the repo has a CLAUDE.md snippet that sets draft-by-default as a standing instruction.&lt;/p&gt;




&lt;h3&gt;
  
  
  What it covers
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;list&lt;/code&gt; — recent emails in any folder&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;search&lt;/code&gt; — by keyword, sender, subject&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;read&lt;/code&gt; — full body + attachment list&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;send&lt;/code&gt; / &lt;code&gt;reply&lt;/code&gt; / &lt;code&gt;reply-all&lt;/code&gt; / &lt;code&gt;forward&lt;/code&gt; — with &lt;code&gt;--draft&lt;/code&gt; support&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;move&lt;/code&gt; / &lt;code&gt;delete&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cal-list&lt;/code&gt; — calendar events in a date range, including shared calendars&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Limits
&lt;/h3&gt;

&lt;p&gt;Worth stating plainly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Windows only. COM is a Windows interface.&lt;/li&gt;
&lt;li&gt;Classic Outlook Desktop App only — not the new Outlook web wrapper, not OWA, not Mac.&lt;/li&gt;
&lt;li&gt;Outlook must be open and signed in when the script runs.&lt;/li&gt;
&lt;li&gt;Single machine, single Outlook profile. Not for headless or multi-mailbox server scenarios.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of those constraints rule you out, use Microsoft Graph.&lt;/p&gt;




&lt;h3&gt;
  
  
  The repo
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/ChiefStarKid/claude-outlook-bridge" rel="noopener noreferrer"&gt;github.com/ChiefStarKid/claude-outlook-bridge&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AGENTS.md and llms.txt are in there for anyone who wants to wire this into a coding agent automatically.&lt;/p&gt;

</description>
      <category>python</category>
      <category>productivity</category>
      <category>automation</category>
      <category>claude</category>
    </item>
  </channel>
</rss>
