# Sessy: a self-hosted view of what AWS SES did with your email

> Sessy is a Ruby on Rails application that puts a delivery timeline, bounce diagnostics and event search in front of raw Amazon SES, with an MCP endpoint so coding agents can query the same data. It is a good fit for teams already sending through SES who want visibility without a hosted email platform.

**marckohlbrugge/sessy** — Open-source email observability for AWS SES

- Repository: https://github.com/marckohlbrugge/sessy
- Website: https://sessy.do
- Stars: 942 · Forks: 45
- Language: Ruby
- License: NOASSERTION
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/marckohlbrugge-sessy

## Sessy watches what happens after the SES send call returns

The problem Sessy is built around is narrow and real. Amazon SES is described as a fantastic email service: cost-effective, reliable, with good deliverability. What it does not give you is a view of what happened to a particular message after you handed it over. The README puts it plainly: it is frustratingly difficult to see what is actually happening with your emails.

That gap is what a lot of people fill by moving to a hosted email service, and the README's complaint about those is that many are overpriced wrappers around SES with a nicer interface. The argument for Sessy is that the interface is the part worth having and the service does not need to be.

Concretely, Sessy collects the events that SES produces and shows deliveries, bounces, complaints, opens and clicks. The screenshot in the repository and the MCP feature description both point at the same object: a message with a full delivery timeline, plus the diagnostic detail behind a bounce.

The audience is therefore engineers whose application already talks to SES through raw API calls or a gem, and who want observability rather than a new sending path. Sessy is not an email sending service and does not try to be. It is the layer that tells you what the sending service did on your behalf, which is a different product with a different maintenance cost.

## The MCP endpoint exposes event search, timelines and aggregate stats

The most distinctive feature in the repository is the MCP server, served at the /mcp path, aimed at AI coding agents such as Claude Code, Cursor and Codex.

The capabilities listed are concrete. An agent can search events, inspect a message's full delivery timeline with bounce diagnostics, and pull aggregate statistics. Beyond read access, agents can create and rename sources and fetch the SES wiring details, meaning the webhook URL, the configuration set name and the topic names, so an agent can set up a new application end to end without leaving the conversation.

The access model is deliberate. Email data is read-only over MCP, and the things that change configuration, namely retention settings and deleting sources, stay in the web UI. That split is a reasonable answer to the question of what an agent should be allowed to destroy.

Connection requires an API key created on the API keys page in the web UI, and the instance serves connect instructions at /docs/mcp. The README's example for Claude Code is:

```bash
claude mcp add --transport http sessy https://your-sessy-host/mcp \
  --header "Authorization: Bearer YOUR_API_KEY"
```

Two operational warnings in the README are worth repeating, because both fail silently in a way that looks like a broken agent. Cloudflare and other CDN users hit a wall, because bot protection with managed challenges blocks MCP clients, and the fix is to exempt the /mcp path from bot protection. The second is a security wrinkle: /mcp authenticates with API keys only and ignores the HTTP_AUTH_* settings, and enabling HTTP Basic auth later does not revoke API keys that were already created, so the API keys page needs a review after you lock an install down.

## One docker run gives you a Sessy instance

The README says the easiest way to run Sessy is with Docker, and gives the whole invocation:

```bash
docker run -p 80:80 \
  -e SECRET_KEY_BASE=$(openssl rand -hex 64) \
  -e DISABLE_SSL=true \
  -v sessy:/rails/storage \
  ghcr.io/marckohlbrugge/sessy:main
```

Read the pieces. The container publishes port 80 to the host, the Rails secret key is generated inline with openssl, SSL is disabled because the container is expected to sit behind a reverse proxy that terminates TLS, and a named volume is mounted at /rails/storage so uploads and local state survive a container replacement. The image is published on GitHub Container Registry with the main tag.

The Dockerfile at the repository root is written for production rather than development and says so in a comment. It pins the Ruby version through an ARG, with the default at 3.4.8, and a comment warns that the value must match the Ruby version in .ruby-version. The base image is ruby on slim, with jemalloc preloaded for reduced memory use and latency, and a throw-away build stage keeps the final image smaller. Packages installed in the base layer include libsqlite3 and postgresql-client alongside libvips and jemalloc.

Full configuration options live in the Docker deployment docs at docs/docker-deployment.md. The same docs directory carries a Kamal deployment guide for running a modified fork, a Dokku guide, an AWS SES setup guide, and a page of SES security and deliverability best practices. That last document is where the hardening questions belong, since the application is only as safe as the sending configuration behind it.

## The hosted edition lives in the same repository, on purpose

The repository contains something unusual for an open source project: the code for a managed version, kept in the open rather than split into a private repository.

The README names the artefacts. There is a saas/ directory, a Gemfile.saas, and an occasional Sessy.saas? check scattered through the code. The stated reason for keeping them is simplicity, rather than maintaining separate repositories, and the same paragraph says none of it affects self-hosting: the default bundle ignores the saas/ engine entirely, and the test suite verifies that the open-source version behaves identically without it.

That last claim is the one that matters for anyone evaluating the project, because it converts a licensing and provenance question into a testable one. The Dockerfile supports the split too, with a build argument for the bundle gem file and a comment saying the default produces the pure open-source image.

The trade-off is that a self-hoster inherits a repository with two products in it. The code paths are guarded and the bundle excludes the engine, but the mental model is more complex than a repository containing one thing, and anyone auditing the codebase has to decide for themselves which parts are in scope. The README also says a managed version is being worked on for people who would rather not run their own instance, which means the two editions will keep moving.

## Background jobs run on Solid Queue with a dashboard at /jobs

Email observability is not a request and response service. Events arrive asynchronously, some processing fails and needs retrying, and some work repeats on a schedule. Sessy handles that with Solid Queue, the Rails background job backend, and exposes a web dashboard at /jobs.

What the dashboard offers is exactly the set of things you need when a queue is misbehaving: monitoring the queues, retrying failed jobs, and viewing recurring tasks. For a system that receives events from outside, this is the part that turns silent failures into visible ones, since an SES event that failed to process is otherwise just absent from the timeline you are trying to read.

The Rails shape shows elsewhere too. The repository root carries config.ru, a Rakefile, Gemfile and Gemfile.lock, a .ruby-version file, a .rubocop.yml for linting, and standard app/, config/, db/, lib/, test/ and vendor/ directories. Background work and a database are prerequisites, not optional extras, which is worth understanding before sizing an instance.

For development, the README gives two commands:

```bash
bin/setup
bin/dev
```

That is the standard Rails development bootstrap, and the contribution guide asks that code changes match the existing style, which in practice means the rubocop configuration is the tie-breaker when a pull request and the linter disagree.

## A custom licence and contribution rules that ask you to talk first

The licence is the first thing a reader should check, and here the metadata and the README need both.

The licence field on the repository says Other, which the platform cannot classify, and points to a licence file. The README names that file LICENSE.md and the licence itself the O'Saasy License, and adds an exception: where a subdirectory specifies otherwise, the terms there apply, with the Claude and Cursor plugin packages named as an example, licensed MIT under .claude-plugin/LICENSE. Since the O'Saasy License text is not reproduced in the README, anyone planning to run this in production needs to read LICENSE.md rather than infer terms from the label.

The contribution rules are equally specific, and they tell you what kind of project this is. The README says the project is still in a very early stage and lists the expectations. Typos and obvious bugs can go in as a direct pull request. Code changes should try to match the existing style. New features need an issue opened first, before implementation. And deployment docs are deliberately restricted: first-party deployment docs stay focused on broad open self-hosted paths the maintainers actually use, such as Docker, Kamal and Dokku, and provider-specific deployment guides are generally not added.

The last point is a design decision about the repository's future, not just its docs. It signals that the maintainers want the deployment surface to stay small and generic. It also means that if you deploy on something the maintainers do not use, you are maintaining that path yourself, which is a real cost to factor in.

## Where Sessy is the wrong tool

Several honest limits.

It only works if you already use Amazon SES. Sessy reads the events SES produces, so a team sending through another provider gets nothing from it, and switching providers to get a dashboard would be a strange trade. The README also frames the target in terms of SES configuration: the wiring details an agent can fetch are the webhook URL, the configuration set and the topic names, so a source is a configured SES destination rather than a mailbox.

It is not a sending layer and not a replacement for one. The value is entirely in the after-the-fact view. If your problem is deliverability configuration, the repository points you at the AWS SES setup guide and the best practices document, because no dashboard fixes a misconfigured domain.

It adds operational weight. A Rails application with a database, a job queue, a web UI and now an MCP endpoint is more to run, patch and secure than the SES console. The web UI is exposed on port 80 by the example command, with SSL disabled, so a reverse proxy with TLS is effectively part of the deployment.

And the project says so itself. The contributing guide describes Sessy as still in a very early stage, and the absence of GitHub releases means there is no version series to pin, no upgrade notes to read and no changelog to diff. The alternative approach for a team with the engineering capacity is to build the observability layer themselves: configure SES to publish its events, ingest them into a store you own, and query that. The difference is that you own the maintenance and the schema, while Sessy gives you a working Rails application, a timeline view and an agent-facing API today, at the cost of a licence you need to read and a codebase that is still moving.

## Conclusion

Adopt Sessy if you already send through Amazon SES and have outgrown staring at event notifications, if you want one interface for deliveries, bounces, complaints, opens and clicks, and if you are willing to operate a Rails app with Docker, Kamal or Dokku. Do not adopt it if you send through another provider, if you need a hosted service rather than a self-hosted one, or if you need a stable contract: the contributing guide describes the project as still in a very early stage. Before deploying, read LICENSE.md, because the licence field on the repository says Other and the README names the O'Saasy License, and check the Docker deployment docs for the environment variables your SES setup needs.

## FAQ

### How do I run my own Sessy instance?

The README gives a single docker run command that publishes port 80, generates SECRET_KEY_BASE with openssl rand -hex 64, sets DISABLE_SSL=true and mounts a named volume at /rails/storage, using the image ghcr.io/marckohlbrugge/sessy:main. Full options are in the Docker deployment docs.

### What can an AI agent do through the Sessy MCP server?

Agents connecting to the /mcp endpoint can search events, inspect a message's delivery timeline with bounce diagnostics, pull aggregate stats, create and rename sources, and fetch SES wiring details such as the webhook URL, configuration set and topic names. Email data is read-only over MCP, while retention settings and source deletion stay in the web UI.

### Why does my Sessy MCP client fail behind Cloudflare?

The README states that bot protection with managed challenges blocks MCP clients, so the /mcp path has to be exempted from bot protection. It also notes that /mcp authenticates with API keys only and ignores HTTP_AUTH_*, and that enabling HTTP Basic auth later does not revoke API keys already created.

### What licence is Sessy released under?

The README says Sessy is released under the O'Saasy License in LICENSE.md, except where a subdirectory specifies otherwise, with the Claude and Cursor plugin packages given as MIT under .claude-plugin/LICENSE. The licence field on the repository says Other and classifies nothing on its own, so read LICENSE.md before deploying.

### Can I deploy Sessy without Docker?

The repository carries a Kamal deployment guide at docs/kamal-deployment.md for deploying a modified fork and a Dokku deployment guide at docs/dokku-deployment.md. The contribution guide notes that first-party deployment docs stay limited to the broad self-hosted paths the maintainers use.

## Sources

- [Issues](https://github.com/marckohlbrugge/sessy/issues)
- [marckohlbrugge/sessy on GitHub](https://github.com/marckohlbrugge/sessy)
- [Project website](https://sessy.do)
- [README](https://github.com/marckohlbrugge/sessy/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/marckohlbrugge-sessy
