Claude and My Email

I declared bankruptcy on my personal inbox a long time ago. There were tens of thousands of emails sitting in there, and my various folders and rules were only partly helping. Since I’ve been having fun with AI for quite some time now, I thought I might try to do something about it. Since I have multiple different providers (Google, Apple, my own mail server, and so on) I decided to have IMAP and SMTP be the common denominator.

I am very hesitant to run OpenClaw on any of the computers on my network, and I’m mostly spending time in Claude Code and the Claude App anyways, so I decided to build an MCP server with tools needed to facilitate the workflow, and then create a skill that can be scheduled in the Claude App. I quite like having the results rendered in proper tables and structured summaries.

The MCP Server

The first version was slow and token-heavy. I quickly realized that mail headers are enormous these days, so I changed the triage flow to be a bit more lightweight while still keeping access to the full headers when needed. There is still a lot of useful information in them.

I also discovered that the Claude app won’t accept binary data from MCP servers other than images, which meant I couldn’t directly access information in PDF attachments. Fortunately, there are great Java libraries for extracting text from PDFs, so adding that support was straightforward.

To make email reading more efficient, HTML emails are converted to Markdown. That cuts the size dramatically, whilst still preserving important structural cues for the AI.

Since this is an MCP server, I also didn’t want to require people to need to have a full Java runtime installed. And since the server doesn’t do especially heavy processing, peak runtime performance wasn’t the main concern. I decided to build it as a GraalVM native image and optimize for memory footprint and startup time instead.

Here is the MCP server, with instructions on how to install it in both Claude Code and the Claude App:
https://github.com/thegreystone/mcp-email

In early experiments, after interacting with it a bit, I asked it whether there were any email conversations with my friends where I dropped the ball. It identified two. Correctly. Ugh. I also asked it things like:

Please find any emails from my tax lawyers around the taxation year 2025, identify all the necessary documentation asked for, and provide an action plan for getting that documentation.

It did very well. Being able to read PDF attachments turned out to be especially useful.

I also often ask it to prepare drafts for me, especially when I need to communicate in German, for example with authorities or businesses. The draft ends up in the Drafts folder, properly set up with the right thread, recipients, and so on, leaving me only to decide whether to send it as-is or make a few edits first.

Initially, this was my favourite prompt:

Please help triage the emails in my inboxes. As per usual, flag anything that is actionable. Don’t hesitate to read emails that you are uncertain about. For anything actionable, provide an action plan. For anything non-actionable, file according to the folder layout.

Adding a Skill

Once the MCP server is installed, adding a skill to Claude is as easy as asking it to create one for itself. Claude has a skill-creator tool that it can use for this.

I asked it to do something along these lines (it was actually edited and improved a few times iteratively, so this is a rough approximation):

Please create a skill using the skill-creator that triages the email across all my accounts in parallel. Flag anything that is actionable. Don’t hesitate to read emails that you are uncertain about. For anything actionable, provide an action plan. For anything non-actionable, file according to the folder layout. At the end of the triage, provide a summary of everything that was done by folder.

The skill should:

  1. Load a persistent ACTIONABLE.md file at the start of each session (path configurable), carry forward unresolved items, and update it at the end.
  2. Run triageCompact on all accounts in parallel (unread first, then a pass over read-but-unfiled emails).
  3. Read full emails when context is needed (e.g. financial, GitHub notifications, ambiguous senders, shipping, calendar invites).
  4. Identify and move spam (will be used to train SpamAssassin later), leave pre-classified Junk alone.
  5. Flag actionable emails before filing.
  6. File everything according to the existing folder hierarchy (if an additional folder would be useful, suggest it).
  7. Cross-reference flagged emails against open ACTIONABLE.md items to detect resolutions.
  8. Produce a structured summary with action plan.

Key resolution logic: Only mark items resolved with positive evidence (delivery confirmed, payment received, PR closed, reply sent). When in doubt, keep open and ask.

Initially, the skill had no memory between sessions. Each scheduled run would rediscover the same actionable items, and it couldn’t tell whether something had been resolved since last time. This became obvious when I asked about an ongoing conversation with my tax lawyers; Claude couldn’t find it because the earlier session was no longer accessible. The fix was simple: a persistent ACTIONABLE.md file that the skill reads at the start of each triage and updates at the end. Open items carry forward, resolved ones get cleared (but only with positive evidence — a delivery confirmed, a payment received, a reply sent), and new items get added. Getting the file location right took a couple of iterations. Claude initially computed it via a convoluted relative path before I pointed out that with the workspace folder configured, ./ACTIONABLE.md was all it needed. It’s just a Markdown file on disk, but it gives the scheduled runs the continuity they were missing.

A quick note on SpamAssassin: the reason I move detected spam to a separate folder rather than deleting it is that I use it to train SpamAssassin. Over time, the obvious spam gets caught before it ever reaches Claude, saving tokens and triage effort.

With the skill installed in both Claude Code and the Claude App, there is no longer any need to write long prompts every time. You just run the skill.

Claude Cowork

With a skill, it becomes very easy to run the triage on a schedule. Simply go to the Cowork tab in Claude App, click on Scheduled on the left, and hit New task on the right.

image

Add the prompt you want to run, for example:

Triage my emails using the email skill.

Fill out the details, like the frequency (e.g. daily), the time (e.g. 18:30), the model to use for the work, and a folder where you want Claude to have read and write access.

Considerations

Let’s be clear about what this setup does: it autonomously moves, files, and flags your email. If it gets something wrong, an important email could end up in the wrong folder or, worse, in Spam. You should go into this with your eyes open.

I would not have tried this without a solid backup strategy for my emails. I do regular IMAP backups, so if something goes sideways, I can recover. That said, I’ve been running this daily for over two months now and haven’t lost anything. The skill is also deliberately conservative — it prefers moving emails to deleting them, and requires explicit approval for deletions.

You may also want to think about where you run the MCP server. I run it on a Mac mini that I mostly use for testing and that doesn’t contain anything sensitive. Running it in a virtual machine, or at least under a user account with tightly scoped permissions, is worth considering. The MCP server holds the IMAP and SMTP credentials and connects directly to your mail servers; Claude never sees those credentials. It only interacts with your email through the tools the MCP server exposes. This isn’t fundamentally different from how any email client works — Mail.app, Thunderbird, and Outlook all hold your credentials and maintain persistent connections to your mail servers. The difference is that this one takes actions autonomously, so please treat it accordingly.

There’s also the question of trust. The skill does a genuinely impressive job. It correctly identified dropped conversations with friends, it handles German-language correspondence with authorities, and it went from 30,000+ emails to fewer than ten in my inbox at any given time. But “impressive” isn’t “infallible.” I review the triage summaries after each run, and I’d recommend you do the same, at least until you’ve built up confidence in how it handles your particular email patterns. I may have to eat these words one day, but today I trust it to do a better job than me at keeping track of my ongoing email conversations.

TL;DR

I declared email bankruptcy at 30,000+ unread emails and built an MCP server exposing tools over IMAP/SMTP so Claude can triage, file, and draft replies across all my accounts — no third-party service required. A scheduled skill in Claude Cowork now runs the whole workflow daily, keeping my inbox down to a handful of actively tracked items.

AI Assistant for OpenJDK Contributors

Subtitle: ”Soon You’ll Know What I Did This Summer”

This summer my family and I decided to stay in Küssnacht during my summer vacation. The summers here are lovely, and for a family of six going for a vacation abroad it typically means a lot of time is spent preparing, packing and unpacking, for both directions of the journey. A staycation seemed like a good choice. Most days were spent in the water with the family, and on one occasion, we were joined by a team member and his family. It can get rather hot here, and the lake is the perfect place to cool down.

Me on a SUP overlooking part of my family on another SUP on Lake Lucerne

That said, I did end up in front of my computer a bit in the evenings after everyone else had fallen asleep. There were a few things I really wanted to get done this vacation. One was upgrading my server. Another one was to put some left over Raspberry Pi 4s and a few new Raspberry Pi 5s to good use in my 19” rack, creating my very own a little Kubernetes cluster.

Recently I was also playing with Custom GPTs, which is OpenAI’s way of letting you build little RAG (Retrieval Augmented Generation) systems without writing code. I first played a bit with Custom GPTs on a train ride back from the Datadog office in Paris a little while ago, and it was a quite lot of fun, but for some of the things I wanted it to do, it performed rather badly, and I couldn’t find any fitting APIs available to help my Custom GPT do better.

Since I was setting up a Kubernetes cluster anyway, I thought I might as well hack something together to make my OpenJDK Project Assistant a little bit better.

Setting Up the Cluster

Ah, the pain and the anguish. I ended up using my Apache server as a proxy (since I still want https://hirt.se to serve my old homepage and blog) for an Nginx that is acting as reverse proxy and load balancer to my Kubernetes cluster.

If anyone wants to know how I set up the cluster, that is an entirely different blog that probably already exists in a thousand versions, written by people with more patience to write about configuration than me, so I will not dwell on that. Suffice to say that I consulted many blogs and Claude 3.5 Sonnet to get it all up and running. Here are the steps in short:

  • Format and setup the Raspberries with Raspberry Pi OS (64-bit) (I used the Raspberry Pi Imager).
  • I added my Pis to the hosts file of my server and to my dhcpd config by MAC address, so that I can refer to the Pis by name instead of IP (I’m using dnsmasq).
  • I installed kubernetes on them, which involved properly enabling cgroups in /boot/firmware/cmdline.txt, setting up containerd properly, getting the debian package for k8s, installing kubelet, kubeadm, kubectl, turning off swap, and finally using kubeadm init to create the cluster and then applying flannel. Then using the join command you get from performing the init to have the other Pis join the cluster.

There was a bunch of trial and error, and there is probably a much easier way. Anyway, I now have a cluster. I can easily deploy. I can easily scale. What more could you ever wish for?

The API exposed to the CustomGPT is available to the world at https://api.hirt.se and described here, should anyone be interested. The implementation is available on GitHub – feel free to contribute if the custom GPT isn’t doing what you think it should be doing or if you simply want to make it more capable.

What Can the OpenJDK Project Assistant Do?

It can do lots of things. It can get information about open PRs and repos without overwhelming GPT with information that will invariably make it fail (this is what typically happened before the api.hirt.se API). For example, here’s getting the oldest open PR for the JDK project:

image (3)

It can summarize the information in the PRs:

image (4)

 

You can also ask for information about the related bug:

image

It can answer questions around people involved in OpenJDK:

image

For people like me, who have a really hard time remembering names, it can help answer questions about people involved in OpenJDK:

image

And about projects and groups:
image

Please let me know if you find it useful!

Summary

  • I recently had a stay-at-home vacation in Küssnacht.
  • Aside from spending a lot of the time in Lake Lucerne with my family, I did have some fun building a Kubernetes cluster out of my Raspberry Pis.
  • I put the cluster to good use to support my OpenJDK Project Assistant Custom GPT.
  • If you’re one of the few OpenJDK committers that also have access to Custom GPTs, or you simply like the idea, feel free to put a star on my API repo, or even better – contribute things you want it to be able to do, or do better!
  • Silly bonus: I also noticed that the silly old slogan image generator I was using on my homepage isn’t available under https (after my various upgrades, I now have permanent redirects to always use https). I couldn’t find any replacement that worked as a drop-in replacement, so I made my own variant that is now also running in my little cluster. Smile

Picture looking out over lake Lucerne after vacation dinner with my wife.