What Miel does
It syncs messages from your Gmail account and stores them locally in Postgres.
It sends each message's sender, subject and preview to the AI for triage, which returns a priority and suggested labels along with its reasoning.
It surfaces the triaged inbox in a web UI where you can apply label suggestions, archive or trash threads, draft replies, and manage Gmail filters.
Who it is for
People with a noisy Gmail inbox who are comfortable running a Docker container, a Postgres database, and a couple of dev servers on hardware they control.
If you would rather not run software yourself, Miel is not for you — there is no hosted version to point you at.
Self-hosted and open source
The whole thing is open source. Clone the repository, read exactly what it does with your mail, and run it yourself.
Your mail, your database, your machine. Nothing is sent to a server I operate.
What you need to run it
Your own Google Cloud OAuth client. Miel talks to Gmail through the Google API on your behalf, and each self-hoster supplies their own client ID and secret — you create a project in the Google Cloud console, enable the Gmail API, and configure a consent screen for your own use. Plan for that setup cost before you start.
Credentials for your AI provider, so Miel can classify your messages and draft replies.
Docker for Postgres, and Bun to run the app.
Motivation
A busy Gmail inbox is mostly noise, and the work of deciding what matters is the part no filter rule ever got right. Rules match senders and subjects; they cannot read a message and tell you it is the one thing worth answering today.
Miel hands that judgement to an AI. It pulls in your recent mail, asks the AI to rank each message by priority and suggest labels, and shows you the result grouped high to low — so the triage pass you were doing by hand is already done when you sit down to it.
Nothing it proposes is applied behind your back. Priorities, label suggestions and new labels worth creating are all proposals; Gmail is not touched until you apply them. The tool does the reading, you keep the decision.
It is built to be self-hosted because mail is the last thing worth handing to someone else's server. You run it against your own Google OAuth client, your own AI credential and your own Postgres, on hardware you control. There is no hosted version and no account with me. The only thing that ever leaves your machine is what goes to the AI provider running your triage, and you pick which one that is: the local Claude Code CLI, which is what a fresh install uses, or Anthropic, Google or OpenAI over their APIs. What each of them is sent is spelled out further down this page.
Installation
Miel runs on Bun, with Postgres in Docker. You will also need a Google Cloud project with the Gmail API enabled and an OAuth client of type Web application.
You will need a credential for one AI provider, and one is already picked for you: claude-code, the local Claude Code CLI, is the default, and a fresh install runs triage, replies and filter suggestions through it. So the last prerequisite is that CLI on your PATH with a token from claude setup-token. Change the provider in settings to Anthropic, Google or OpenAI and the prerequisite becomes an API key from that vendor instead, with no CLI at all.
Two ways to run it: with Bun for development, or entirely in Docker if you would rather not install Bun at all.
Clone the repository and install dependencies
Every dependency comes from a public registry, so the install needs no token and no per-scope configuration.
git clone [email protected]:lucasriondel/miel.git cd miel bun installFill in your environment
Copy the example file and supply three things: your Google OAuth client ID and secret, a random API secret that must match between API_SECRET and VITE_API_SECRET, and in production a TOKEN_ENCRYPTION_KEY. No AI credential goes in this file — nothing miel runs reads one from the environment. Every provider's credential is pasted in the app instead and stored encrypted in Postgres. Run claude setup-token now anyway: its token is required as shipped, since claude-code is the provider a fresh install triages with, and the next step is where you paste it. It is optional only if you switch every task to Anthropic, Google or OpenAI before your first sync. The rest of the file already has working localhost defaults.
cp .env.example .env openssl rand -base64 32 # TOKEN_ENCRYPTION_KEY claude setup-token # paste the result in Settings, not in .envStart Postgres
The dev compose file starts the database on port 5435 and nothing else. Migrations apply themselves the next time the API boots, so there is no separate step for them.
docker compose -f docker-compose.dev.yml up -dRun the app
The root compose file builds and runs Postgres, the API and the web app together — the fastest way to self-host without installing Bun. The API applies any pending migrations itself on start, so nothing else is needed. Rebuild with --build after changing either VITE_API_BASE or VITE_API_SECRET, because both are baked into the bundle at image-build time.
docker compose up -d --buildRun the app
The API comes up on port 3001 and the web app on port 3000. Open http://localhost:3000/app and click Connect with Google.
bun devCheck which AI provider runs each task, and change it if you want
Open Settings, then AI and Triage. All three tasks — triage, replies and filter suggestions — start on claude-code, the local Claude Code CLI, so a fresh install triages from its very first sync once that provider has its credential. Every provider's credential lives under Credentials in that same panel, one row each, stored encrypted in Postgres rather than in a file or an environment variable: an API key for Anthropic, Google or OpenAI, and for Claude Code the token from claude setup-token. Nothing is read from the environment, so a credential exists only once it is pasted here. Point any task at Anthropic, Google or OpenAI instead and that vendor's key is what it needs.
How to contribute
Issues and pull requests are welcome. The repository is the whole product — there is no private fork and no hosted variant with extra code in it.
CONTRIBUTING.md at the repository root is the place to start: how to get the stack running, the checks every pull request is gated on, and the conventions the codebase follows — one component per file, business logic in core services rather than in API routes, external systems reached only through the Effect services in packages/core/src/google and the Claude service beside them. CLAUDE.md beside it has the same ground in full detail, addressed to the coding agents that work on this repository.
Before opening a pull request, keep the checks green. Type checking is the one that matters most, since the codebase is strict TypeScript throughout.
Good first contributions: a Gmail feature the triage flow does not cover yet, a rough edge in the review UI, or documentation that was wrong or missing when you tried to follow it.
Run the checks before opening a pull request
bun run typecheck bun run lint bun run format:check bun run test
What Miel asks Google for
Miel requests five Google permissions. Below is each one: the wording Google itself will show you on the consent screen, and the feature that needs it.
The consent wording is reproduced as Google writes it, not softened. The mail permission is described there as permanently deleting your email — Miel never does that, but you will read that sentence when you connect an account, and you should read it here first.
| Permission | What Google's consent screen says | What Miel uses it for |
|---|---|---|
| Gmail modify | Read, compose, send, and permanently delete all your email from Gmailhttps://www.googleapis.com/auth/gmail.modify | Fetch messages for triage; apply and remove labels; archive or trash the threads you select. |
| Gmail send | Send email on your behalfhttps://www.googleapis.com/auth/gmail.send | Send the replies you have reviewed and approved. Nothing is sent without you pressing send. |
| Gmail basic settings | See, edit, create, or change your email settings and filters in Gmailhttps://www.googleapis.com/auth/gmail.settings.basic | List your Gmail filters, and create the ones you accept from Miel's suggestions. |
| User info: profile | See your personal info, including any personal info you've made publicly availablehttps://www.googleapis.com/auth/userinfo.profile | Show the connected account's display name and avatar in the app. |
| User info: email | See your primary Google Account email addresshttps://www.googleapis.com/auth/userinfo.email | Identify which account is connected. |
Miel never permanently deletes mail: trashing a thread moves it to Gmail's bin, where Gmail's own thirty-day rule takes over. The mail permission still cannot be narrowed while archiving and trashing are features — the read-only and labels permissions together cover neither changing labels in bulk nor trashing a thread.
What Miel sends to the AI provider
Miel does not classify your mail itself. It sends it to the AI provider your deployment is configured against, which returns a priority, suggested labels and its reasoning. Your message content therefore leaves your machine and is processed by that provider under their terms. This is the one part of Miel that is not purely local, and if it is not acceptable to you, do not connect an account.
For triage, each request carries the sender, subject, the short preview snippet Gmail provides and the labels already on the message — up to 15 messages per request by default, and at most 50 if you raise the batch size in settings. Through the local CLI, the provider may ask Miel for a full message body when the preview is not enough to decide; a provider called over its API cannot, and is never sent one.
Drafting a reply is the one request that carries a whole message body, and it is truncated to the first 8,000 characters — anything past that is never sent. Filter suggestions send the sender, subject and snippet only.
You supply your own AI provider credentials, so the account these requests are billed to and governed by is yours, not mine. Read that provider's terms before you connect an account — you choose which one runs each task in settings, and the setup guide names the one Miel uses by default.
What Miel stores, for how long, and how to have it deleted: Privacy policy. The terms it is offered under: Terms of service.
Contact
Questions about Miel, or about what it does with your data: [email protected]