Self-hosted · AGPL-3.0

RecapitoOne inbox for every mailbox you already have.

Connect any number of IMAP mailboxes, read them in a single threaded interface, and send replies through your own provider. A NestJS API, a Next.js web client, an IMAP sync daemon and PostgreSQL — packaged as a Docker Compose stack that runs on your server, not somebody else's.

reh-ka-PEE-toh Italian for delivery — and for the contact details you give someone so they can reach you.

  • NestJS 10 + Next.js 14
  • PostgreSQL 16
  • Docker Compose
  • IMAP IDLE sync daemon

Recapito is not a mail server. It does not listen on port 25, does not receive SMTP, and cannot be the MX host for your domain. It sits on top of mailboxes you already have — Gmail, Fastmail, Zoho, or any IMAP host — and gives you one place to read and answer them.

Set expectations first

What Recapito is, and what it isn't

Getting this wrong is the fastest way to be disappointed by it, so it is worth being blunt.

It is

  • A unified inbox. Add IMAP accounts; Recapito syncs them into one threaded reading and reply interface.
  • Self-hosted. Your server, your Postgres. No hosted tier, no account to create, nothing phoning home.
  • A sending front-end for providers you already use — Mailgun, Brevo, or any SMTP host.
  • Multi-user, with per-user mailboxes and an admin flag for managing accounts.

It is not

  • A mail server. It does not receive SMTP and cannot be your domain's MX host.
  • The authoritative store for your mail — your IMAP provider is. Drop the Recapito database and your mail re-syncs.
  • A replacement for Postfix, Dovecot, Mailcow or Mail-in-a-Box. It sits on top of what they provide.
  • A bulk-mail or marketing platform. There is no campaign builder, list management, or unsubscribe handling.

The architectural bet

Your mail is mirrored locally, so it behaves like local data

Most multi-account mail clients ask your IMAP servers a question every time you search or open a folder, and you wait for the slowest one. Recapito doesn't. A sync daemon mirrors your messages into PostgreSQL as they arrive, and everything the interface does — searching, threading, filtering by label, acting on fifty conversations at once — runs against that local copy.

Your provider stays the source of truth. The mirror is a cache you can throw away and rebuild.

  • Search spans every mailbox at once. One query over threads and messages, not one query per account.

  • Threading is computed once, on write. Conversations are assembled as mail lands rather than reassembled on every page view.

  • Bulk actions don't wait on IMAP. Multi-select and move, archive, mark spam or delete without a round trip per message.

  • New mail arrives by push. The daemon holds an IMAP IDLE connection per mailbox for INBOX, so nothing waits on a poll timer.

Built and working today

Features

Everything below exists in the codebase now. Things that don't are in the roadmap, separately and deliberately.

Unified inbox

Any number of IMAP mailboxes, read as one stream. Per-user accounts, each with its own credentials and send provider.

Threaded conversations

Replies group by the In-Reply-To header, falling back to a normalised subject only when the subject actually identifies something. Messages are de-duplicated on the RFC message ID, so nothing is stored twice.

Folders

Inbox, Sent, Drafts, Spam, Trash and Archive, navigable from the sidebar across every connected account.

Cross-mailbox search

Search threads and messages from every account in one query, served from Postgres rather than from your mail hosts.

Real-time IMAP IDLE sync

A standalone daemon holds an IDLE connection per mailbox for INBOX, so new mail arrives by server push. Sent, Spam and Trash get a sweep every five minutes.

Labels

Categorise threads with your own labels and list every thread carrying one.

Drafts with autosave

Half-written replies persist server-side as you type, so a closed tab is not a lost message.

Scheduled send

Give a draft a send time and a background worker dispatches it — checking every minute, retrying up to three times, through the same delivery path as an immediate send.

Signatures

Multiple signatures per user, one markable as the default.

Reply templates

Save the answers you keep retyping and drop them into a reply with a /shortcut while composing.

Bulk actions

Multi-select threads, then move, archive, mark spam or delete them in one pass. Star and mark-as-read too.

Contacts

An address book with favourites and a frequently-contacted view, built from the mail you actually exchange.

Multi-user with admin

Separate users, each with their own mailboxes, providers and signatures. An admin flag covers user, mailbox and provider management.

Attachments Receiving

Incoming attachments are extracted during sync and written outside the database under a random filename, capped at 25 MB by default. They are listed on the message and downloaded on demand. Adding attachments to an outgoing reply is on the roadmap.

Your choice of sending path

Send through Mailgun, Brevo's transactional API, or any plain SMTP host. Several providers per user, one default, and a per-mailbox assignment. Mailgun delivery and failure events are tracked via signature-verified webhooks.

Health endpoints

/api/health, /api/health/live and /api/health/ready for uptime monitoring, plus keyboard shortcuts in the inbox: c compose, r refresh, / search.

Engineering judgement, not a feature list

Built to be run safely

Recapito holds the passwords to your email accounts. That fact drove several decisions that are deliberately inconvenient, because the convenient version of each one is how self-hosted software quietly leaks mailboxes.

  • Credentials encrypted at rest with AES-256-GCM

    IMAP passwords, SMTP passwords and provider API keys are encrypted before they reach the database, with a fresh random 96-bit IV per value and a version-prefixed envelope so the scheme can be rotated later. Authenticated encryption means tampered ciphertext fails at decrypt time instead of being replayed at a mail server. Login passwords are different — those are bcrypt-hashed and never decryptable, which is correct for a credential the server only ever verifies.

  • Mandatory secrets, with no insecure fallback

    JWT_SECRET and ENCRYPTION_KEY have no default, no fallback and no development shortcut. The API validates both at startup and refuses to boot if either is missing, too weak, or set to a well-known placeholder such as changeme. An application that boots successfully while signing tokens with a guessable secret looks healthy and is completely compromised; failing to start is the safer outcome.

  • Registration closed by default, first account becomes admin

    A self-hosted instance is reachable by anyone who finds the URL, so public sign-up is off unless you set ALLOW_REGISTRATION=true. The very first account is always allowed — otherwise a fresh deployment could never be bootstrapped — and it is granted administrator rights. Everyone after that is created deliberately.

  • Attachments are always downloads, never rendered inline

    An email attachment is untrusted content from an arbitrary sender. Every one is served as application/octet-stream with an attachment disposition, X-Content-Type-Options: nosniff and a restrictive Content-Security-Policy, so a malicious .html or .svg can never execute on the application's origin with your session. The on-disk filename is a random UUID; the sender's filename is a database column, sanitised back into a header at download time.

  • HTML email renders inside a sandboxed iframe

    An HTML message body is arbitrary markup written by whoever sent it. Rather than injecting it into the page, Recapito renders it in an iframe with neither allow-scripts nor allow-same-origin, so the browser refuses to run script in it at all and gives it an opaque origin — a browser-enforced boundary rather than a sanitiser that has to keep pace with new bypasses. A Content-Security-Policy inside that frame blocks remote script outright and allows only data: images, which has the pleasant side effect of stopping tracking pixels quietly reporting that you opened the mail.

The project is equally explicit about the gaps: message bodies are stored unencrypted so that server-side search works, there is no built-in rate limiting on the auth endpoints, and there is no audit log. All of it — plus key-rotation consequences and a deployment hardening checklist — is written up in SECURITY.md.

Four commands

Quick start

You need Docker with the Compose plugin, and nothing else. This brings up Postgres, the API, the web client and the IMAP daemon.

  1. Clone the repository

    git clone https://github.com/iampopye/recapito.git
    cd recapito
  2. Create your environment file

    cp .env.example .env
  3. Generate the secrets

    # Signs session tokens
    echo "JWT_SECRET=$(openssl rand -hex 32)"
    
    # Encrypts stored IMAP and SMTP credentials — back this up
    echo "ENCRYPTION_KEY=$(openssl rand -hex 32)"
    
    # Postgres password — no default is baked into the code
    echo "DATABASE_PASSWORD=$(openssl rand -base64 32)"

    Paste each line into .env. ENCRYPTION_KEY must be exactly 64 hex characters — a 32-byte key. Store it somewhere separate from your database backups: an archive holding both is, functionally, a backup of your plaintext mailbox passwords.

  4. Bring the stack up

    cd docker
    docker compose --env-file ../.env up -d

    The Compose files live in docker/, so --env-file is what points them at the .env in the repository root. Migrations run on API startup — there is no separate migration step.

Open http://localhost:3000 and create the first account, which becomes the administrator. Add a mailbox with its IMAP host, port and credentials; the daemon picks it up and starts syncing. To send, configure Mailgun under Settings, or add a Brevo or SMTP provider under SMTP Providers. DEPLOYMENT.md covers putting it on a real server, with nginx and Let's Encrypt.

Three services and one database

Architecture

Note where the IMAP connections live: the browser never talks to IMAP, and neither does the API. Only the daemon does.

Recapito architecture The browser talks only to the Next.js frontend, which calls the NestJS API over REST. The API reads and writes PostgreSQL and sends mail through Mailgun, Brevo or SMTP, which return delivery webhooks to the API. A separate imap-daemon holds IMAP IDLE connections to your mail providers and writes threads and messages directly into PostgreSQL. Recapito — self-hosted Browser Frontend Next.js 14 · :3000 API NestJS 10 · :3001/api imap-daemon Node · imapflow PostgreSQL 16 the mirror Send providers Mailgun · Brevo · SMTP Your IMAP providers Gmail · Fastmail · Zoho · any host REST over HTTP write threads and messages send mail delivery webhooks IMAP IDLE + fetch
Mermaid source for this diagram
flowchart LR
    User([Browser])

    subgraph self["Recapito — self-hosted"]
        FE["Frontend<br/>Next.js 14 · :3000"]
        API["API<br/>NestJS 10 · :3001/api"]
        DAEMON["imap-daemon<br/>Node · imapflow"]
        DB[("PostgreSQL 16")]
    end

    IMAP["Your IMAP providers<br/>Gmail · Fastmail · Zoho · any IMAP host"]
    SEND["Send providers<br/>Mailgun · Brevo · SMTP"]

    User --> FE
    FE -->|REST over HTTP| API
    API --> DB
    API -->|send mail| SEND
    SEND -.->|delivery webhooks| API
    DAEMON -->|IMAP IDLE + fetch| IMAP
    DAEMON -->|write threads and messages| DB

Frontend

apps/frontend — Next.js 14 App Router, React 18, Tailwind. Reads and writes exclusively through the API.

API

apps/backend — NestJS 10, TypeORM, Passport JWT. Serves everything the UI does, and handles outbound sending.

imap-daemon

apps/imap-daemon — one IMAP IDLE connection per active mailbox, writing straight into Postgres. No HTTP surface of its own.

New-mail detection works on a rolling 30-minute IMAP SINCE window, fetched in batches of 50, de-duplicated on the RFC message ID. A message is never stored twice, and a brief daemon outage is caught up automatically on reconnect.

Not built yet

Roadmap

None of this exists in the codebase today. It is listed here so nobody installs Recapito expecting it — and because these are the best places to start if you want to contribute something substantial.

  • OAuth2 / XOAUTH2 for Gmail and Outlook Top priority

    Mailboxes authenticate with a stored username and password today. Google turned off plain password sign-in for IMAP in 2024, so a Gmail account now needs an app password and a Workspace admin can switch even that off. Token-based authentication is the single change that most widens what Recapito can connect to.

  • Rich-text compose

    Compose is a plain textarea. A proper editor — formatting, links, inline quoting — is not built.

  • Filters and rules

    No automatic categorisation. Labels and folders are applied by hand.

  • Email forwarding

    You can reply and compose. Forwarding an existing message is not implemented.

  • Saved searches

    Search runs, but there is no way to pin a query and come back to it.

  • Internationalisation

    The interface is English-only, with no translation layer to plug other languages into.

  • Sending attachments

    Attachments on received mail are stored, listed and downloadable. Attaching a file to an outgoing reply is not implemented — compose is text only.

  • Test coverage

    Five spec files cover encryption, authentication and user management. The frontend and the daemon have none at all — the most useful unglamorous contribution available.

Before you install it

Frequently asked questions

Is Recapito a mail server?

No. Recapito does not listen on port 25, does not receive SMTP, and cannot be the MX host for your domain. It syncs IMAP mailboxes you already have with providers such as Gmail, Fastmail or Zoho, and sends through Mailgun, Brevo or your own SMTP host. If you want to host mail, you want a mail server such as Postfix, Dovecot, Mailcow or Mail-in-a-Box; Recapito sits on top of one.

Do I have to use Mailgun?

No. Mailgun, Brevo's transactional API and any plain SMTP host are all supported, and they are configured per user in the interface rather than baked into the deployment. You can register several providers, mark one as the default, and assign a specific provider to a specific mailbox. Receiving mail needs no provider at all — only IMAP access to your existing accounts.

Where is my mail actually stored?

Your IMAP provider remains the authoritative store. Recapito mirrors messages into PostgreSQL as they arrive so that cross-mailbox search, threading and bulk actions run against local data instead of querying every mail host live. Dropping the Recapito database does not delete your mail — it re-syncs. Message bodies in that mirror are stored unencrypted, because encrypting them would break server-side search; disk or database-level encryption is the lever for that.

Does it work with Gmail?

Only with an app password today. Mailboxes authenticate with a stored username and password, and Google turned off plain password sign-in for IMAP in 2024, so a Gmail account needs an app password — and a Workspace administrator can disable those. OAuth2 / XOAUTH2 support is the top item on the roadmap for exactly this reason. Any IMAP host that accepts a username and password works now.

Is it ready for production?

Be careful. This is an early project maintained by one person, and it has not been battle-tested at scale. The security fundamentals are deliberate — credentials encrypted with AES-256-GCM, mandatory secrets with no fallback, registration closed by default — but there is no built-in rate limiting on the authentication endpoints, no audit log, and most of the codebase has no test coverage. Read SECURITY.md and work through the deployment hardening checklist before you put it on the internet.

What does the licence let me do?

Recapito is licensed under the GNU Affero General Public License v3.0. You can use it for anything, including commercially, at no cost, and you can read, modify and redistribute the source. The catch is the network clause: if you modify Recapito and let other people use your modified version over a network, you must offer those users the source of your changes under the same licence. Running an unmodified copy, or modifying it purely for your own use, obliges you to publish nothing.

Open to first-timers

Contributing

Contributions are welcome, including small ones and first ones. Documentation fixes, typo corrections and "this instruction did not work for me" issues are genuinely useful — the docs are part of the product.

Start with a good first issue

Issues labelled good first issue are scoped to be completable without knowing the whole codebase. If one is unassigned, comment on it and it is yours — no need to ask permission, and no rush to finish.

Getting stuck is fine

Say so in the thread. A half-finished attempt with a question is more useful than silence, and beginner questions are welcome — ask them in an issue.

Read the guide first

CONTRIBUTING.md covers the development setup, how the three services fit together, where to add a feature, the commit convention and what a good pull request looks like.

Everyone taking part is expected to follow the Code of Conduct. Found a security problem? Report it privately through GitHub security advisories rather than in a public issue.