Skip to content
gettressoPublic

About

Open source email client: every email is something to do, to hand over, or to know.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

610 Commits

Folders and files

Repository files navigation

Tresso: every email is something to do, to hand over, or to know

CI Release Latest release License: AGPL-3.0 macOS, Linux, Windows, web, mobile

Tresso is an open source email client built around a single idea: every email is something to do, to hand over, or to know.
Repo: github.com/gettresso/tresso

Install Β· How it works Β· Highlights Β· Your own server Β· Development Β· Releases


πŸ“¦ Install

The desktop app runs on macOS, Linux and Windows. It signs in to Tresso Cloud, or to your own server.

macOS / Linux

curl -fsSL https://sherin.dev/tresso/install.sh | bash

Pin a release with bash -s:

curl -fsSL https://sherin.dev/tresso/install.sh | bash -s v0.1.0

On macOS the script puts Tresso.app in /Applications (or ~/Applications when that is not writable). On Linux it installs the AppImage in ~/.local/share/tresso, links tresso into ~/.local/bin, and adds a launcher entry that also opens tresso:// links, through which sign-in comes back. TRESSO_VERSION pins a release like the first argument, and TRESSO_INSTALL picks another folder. Run it again to upgrade in place.

Windows

powershell -c "irm https://sherin.dev/tresso/install.ps1|iex"

The script runs the installer silently for your user: Tresso goes to %LOCALAPPDATA%\Tresso and into the Start menu. $env:TRESSO_VERSION pins a release (v0.1.0). Remove it again under Settings > Apps.

Both scripts check the download against SHA256SUMS on the GitHub release before they install anything.

After that, Tresso keeps itself up to date. On macOS it uses Sparkle: it checks every few hours, and Tresso > Check for Updates… asks right away. On Windows and Linux (the AppImage) a card offers the new version. Every update is signed, and the app installs only updates whose signature matches the key built into it. Before it restarts into an update, Tresso saves the draft you are writing and waits for files you are attaching and a message being sent. Settings > Updates shows your version.

Published files

Every release has:

Platform File
macOS (Apple Silicon and Intel) tresso-macos-universal.dmg, tresso-macos-universal.app.tar.gz
Linux x86_64 tresso-linux-x86_64.AppImage, tresso-linux-x86_64.deb
Linux arm64 tresso-linux-aarch64.AppImage, tresso-linux-aarch64.deb
Windows x64 tresso-windows-x64-setup.exe, tresso-windows-x64.msi

Prereleases (v1.2.0-beta.1) ship the Windows setup without the .msi, which only takes numeric versions.

The app is not signed with an Apple Developer ID yet. The install script lifts the macOS quarantine for you. When you open the .dmg yourself, right-click Tresso and choose Open the first time, or run xattr -dr com.apple.quarantine /Applications/Tresso.app. The AppImage needs FUSE 2 (libfuse2, or libfuse2t64 on Ubuntu 24.04 and later); the .deb does not.

🧭 How it works

Mail from every account lands in one inbox; triage settles each email with D, H or K into a todo, a hand-over in Waiting on, or the daily brief
Verdict What happens Done when
Do It becomes a todo, linked to the email, with a due date when there is one The todo is completed and the email archives itself
Hand over It goes to someone else with a note and a due date They finish it; until then it sits in Waiting on
Know It lands in the daily brief One click on Noted

✨ Highlights

  • Unified Inbox across all your accounts, grouped into smart inboxes (To do, Informational, Notifications, Newsletters, Invitations, Spam) plus your own, defined by a name, instructions and sender include/exclude lists.
  • Built-in todos that sync across devices, can be created automatically from email, and get time in your calendar.
  • Triage mode: one email at a time, settled with D, H or K.
  • Calendar next to your mail: invitations answered from the email, events from what an email proposes.
  • One account, every device: settings, mail accounts and HTML signatures stay in sync.
  • Attachment virus scanning before anything can be opened, and your mail encrypted at rest per user.
  • Everywhere: web, desktop (Tauri) and mobile (Expo), with notifications only for mail that needs you.

πŸ”“ Open source and Tresso Cloud

Tresso is fully open source. Everything it does is in this repository, and running it on your own server is free, with every feature. What is still on the roadmap, such as AI with your own key or endpoint, teams with shared inboxes, webhooks and MCP tools for mail, todos and the calendar, lands here too.

Tresso Cloud is the managed hosting of that same code: a subscription pays for running it for you, with backups and support, never for features. See Self-hosted and Tresso Cloud and Billing.

🏠 Your own server

Every release publishes the server as images on GitHub's container registry: ghcr.io/gettresso/tresso-api (the API and its workers), tresso-sync (the sync workers) and tresso-web (the web client), for amd64 and arm64. Tresso Cloud runs the same images. To run them, you need a server with Docker, two DNS names pointing at it (one for the web client, one for the API) and ports 80 and 443 open:

  1. Download compose.prod.yaml and .env.prod.example into one folder.

  2. Copy .env.prod.example to .env and fill it in: your two addresses and every secret, each generated (openssl rand -hex 32). Keep a copy of DATA_KEYS somewhere safe: it encrypts the mail at rest, and losing it loses that mail. The API refuses to start while a secret is missing, too short, or one of the development values in this repository, or while an address is not a plain https:// address.

  3. Start it:

    docker compose -f compose.prod.yaml --env-file .env up -d
  4. Create your account:

    docker compose -f compose.prod.yaml exec api php bin/console app:user:create you@example.com "Your Name"

The stack gets HTTPS certificates by itself, migrates the database on start and runs PostgreSQL, Redis, object storage (RustFS), Meilisearch and ClamAV next to Tresso; ClamAV needs about 1.5 GB of RAM. To upgrade, set TRESSO_VERSION (or keep latest) and run docker compose -f compose.prod.yaml pull before up -d. Two settings only apply the first time: POSTGRES_PASSWORD (the database keeps its password) and OAUTH_PASSPHRASE (it encrypts the OAuth signing key; a new passphrase needs a new key: remove both files from the tresso-server_oauth_keys volume and restart, after which the apps renew their sign-in).

Running the images without this compose file, set the same settings as compose.prod.yaml does. Exactly one API container (running the image's default frankenphp command) needs RUN_MIGRATIONS=1: on start it makes the OAuth signing keys, migrates the database and creates the OAuth clients. Every other container, more API containers and the workers, needs those keys too (mount them read-only at /app/config/jwt). CORS_ALLOW_ORIGIN and MERCURE_CORS_ORIGINS list the allowed origins separated by spaces: the web address and the desktop app's own, tauri://localhost and http://tauri.localhost.

In the desktop app, choose Use another server on the sign-in screen and enter the API address, such as api.mail.example.com. The app checks that the server answers and remembers it until you choose again. The web client is at your web address.

πŸ€– Use Tresso from Claude and other MCP clients

The API has an MCP endpoint at /mcp, so Claude and other MCP clients can change your settings for you: signatures and the default signature of each mail account, smart inboxes and their senders, notifications, batches, the daily brief, reminder rules, calendars and more. A client signs in with your consent and only gets the permissions you allow; every tool does exactly what the API allows with that permission. Mail, todos and events are coming next.

Tresso Cloud. In Claude Code:

claude mcp add --transport http tresso https://api.gettresso.com/mcp

Then run /mcp in Claude Code and choose Authenticate: your browser opens Tresso's sign-in and a screen that asks which permissions to give. In Claude.ai (or Claude Desktop), add a custom connector under Settings, Connectors with the URL https://api.gettresso.com/mcp. On your own server, use your API address instead, such as https://api.mail.example.com/mcp.

Locally (the development stack below):

claude mcp add --transport http tresso http://localhost:8080/mcp

Or skip the sign-in with a personal access token (Settings, Access tokens, with the scopes the tools need):

claude mcp add --transport http tresso http://localhost:8080/mcp --header "Authorization: Bearer tresso_pat_..."

Settings, Connected apps lists the apps you allowed and disconnects them. See Authentication and authorization for the tools and the scope each one needs.

🧱 Stack

Part Technology
API PHP 8.5, Symfony 8.1 on FrankenPHP (worker mode), Doctrine, Messenger, OAuth2 server
Sync workers Go 1.27
Web Vue 3, Vite, TypeScript, Pinia
Desktop Tauri 2 (Rust) wrapping the web app
Mobile Expo (React Native)
Data PostgreSQL 18, Redis, S3 storage (RustFS locally), Meilisearch
Scanning ClamAV

πŸ“ Repository

apps/
  api/        Symfony API: OAuth2 server, domain, background jobs
  sync/       Go sync workers
  web/        Vue 3 web client
  desktop/    Tauri 2 shell around apps/web
  mobile/     Expo app
  website/    Marketing site at gettresso.com (Astro)
packages/
  core/       Shared TypeScript: domain types, OAuth2 PKCE client
docker/       Dockerfiles and server config
docs/         Architecture and roadmap
scripts/      Install scripts for the desktop app
assets/       README images

πŸš€ Getting started

You need Docker, Node 24 or newer, and Rust for the desktop app. Go and PHP run inside containers.

  1. Start the stack:

    docker compose up -d

    On first boot the API installs its dependencies, generates OAuth2 signing keys, runs the migrations and creates the first-party OAuth2 clients (web, desktop, mobile, sync-worker).

  2. Create a user (you are asked for a password):

    docker compose exec api php bin/console app:user:create you@example.com "Your Name"
  3. Install the JavaScript workspaces and start the web app:

    npm install
    npm run dev:web

    Open http://localhost:5173 and sign in.

The desktop and mobile apps start with:

npm run dev:desktop
npm run dev:mobile

Gmail and Microsoft 365

Connecting Gmail and Microsoft 365 accounts needs an OAuth app at Google and at Microsoft, registered by whoever runs the server. docs/ARCHITECTURE.md lists the steps. In short:

  • Redirect URIs: http://localhost:8080/connect/google/callback and http://localhost:8080/connect/microsoft/callback for the Docker stack (<API_PUBLIC_URL>/connect/<provider>/callback elsewhere).

  • Scopes: Google openid email profile https://www.googleapis.com/auth/gmail.modify (with the Gmail API enabled); Microsoft Graph delegated openid email profile offline_access User.Read Mail.ReadWrite Mail.Send.

  • Put the client ids and secrets in apps/api/.env.local:

    GOOGLE_OAUTH_CLIENT_ID=...
    GOOGLE_OAUTH_CLIENT_SECRET=...
    MICROSOFT_OAUTH_CLIENT_ID=...
    MICROSOFT_OAUTH_CLIENT_SECRET=...
    MICROSOFT_OAUTH_TENANT=common

Without them the web app only offers IMAP accounts.

Local services

Service Address
API http://localhost:8080 (health check at /health)
Web app http://localhost:5173
Mailpit (outgoing mail) http://localhost:8025
RustFS (S3) http://localhost:9000, console on 9001
Meilisearch http://localhost:7700
Postgres localhost:54320 (user, password and database: app)
Redis localhost:63790
GreenMail (test mail server) IMAP localhost:3143, IMAPS 3993, SMTP 3025, SMTPS 3465

The sync worker in the dev stack has MAIL_ALLOW_UNSAFE_SERVERS=1 so it can reach GreenMail on the Docker network; in production that must stay off. The integration tests empty the kim@tresso.test mailbox.

GreenMail has two mailboxes for development, kim@tresso.test (password dev-password-kim) and lisa@tresso.test (dev-password-lisa). Connect them in the app with host greenmail, IMAP port 3143 and SMTP port 3025, security none.

ClamAV needs about 1.5 GB of memory, so it only starts when asked. CLAMAV_HOST points the API at it; without it, attachments are marked as not scanned and open only after a warning. ATTACHMENTS_REQUIRE_SCAN=1 removes that override (and then needs a scanner: the health check fails without one). Scans run in their own worker, scanner.

CLAMAV_HOST=clamav docker compose --profile scan up -d

After a signature update, or once clamd is back after scans failed, scan attachments again with php bin/console app:attachments:rescan --all (or --user=<email>, optionally --status=failed).

πŸ”§ Development

Create the test database once:

docker compose exec -e APP_ENV=test api sh -c 'php bin/console doctrine:database:create --if-not-exists && php bin/console doctrine:migrations:migrate -n'

Then run the checks for the part you changed:

Part Command
API tests (per file) docker compose exec -e APP_ENV=test api php bin/phpunit tests/Functional/SyncLeaseTest.php
API static analysis docker compose exec api sh -c 'bin/console cache:warmup --env=test && vendor/bin/phpstan analyse'
API docs (dev only) http://localhost:8080/docs
Sync worker docker run --rm -v "$PWD/apps/sync":/src -w /src golang:1.27 go test -race ./...
Sync worker against GreenMail docker run --rm --network tresso_default -e GREENMAIL_HOST=greenmail -v "$PWD/apps/sync":/src -w /src golang:1.27 go test -race -tags integration ./...
Shared core npm test --workspace @tresso/core
Web npm run test:unit --workspace @tresso/web and npm run type-check --workspace @tresso/web
Desktop cargo check in apps/desktop/src-tauri (on macOS first node apps/desktop/scripts/fetch-sparkle.mjs, which the Tauri commands also run)
Mobile npx vitest run, npx tsc --noEmit and npx expo lint in apps/mobile

Every push and pull request runs all of these in CI.

Commits follow Conventional Commits with a required scope from .agents/commit-scopes.md, for example feat(api): add todo endpoints.

Good to know

  • FrankenPHP keeps the app in memory. After clearing the cache inside the container, restart it with docker compose restart api.
  • After compose.yaml changes (new environment variables, for example), recreate the services with docker compose up -d: restart keeps a container's old environment.
  • Secrets in compose.yaml and the committed .env files are development values only. Real secrets go in .env.local files or the deployment environment. In production set at least APP_SECRET, OAUTH_PASSPHRASE, OAUTH_ENCRYPTION_KEY, SYNC_WORKER_CLIENT_SECRET, DATA_KEYS and DATA_KEY_CURRENT, the Mercure hub settings (MERCURE_PUBLIC_URL, MERCURE_INTERNAL_URL, MERCURE_CORS_ORIGINS and two different keys of at least 32 bytes in MERCURE_PUBLISHER_JWT_KEY and MERCURE_SUBSCRIBER_JWT_KEY), the object storage settings (S3_ENDPOINT, S3_REGION, S3_BUCKET, S3_ACCESS_KEY, S3_SECRET_KEY), and keep MAIL_ALLOW_UNSAFE_SERVERS=0.
  • Desktop and mobile sign in through the tresso://auth/callback scheme: the system browser signs in and the link brings the result back to the app. Windows and Linux register the scheme when npm run dev:desktop starts; macOS only takes it from an app bundle, so to sign in there build one with npm run tauri --workspace @tresso/desktop -- build --debug --bundles app and open the .app it reports.
  • The mobile app signs in only from a development build (npx expo run:ios or npx expo run:android in apps/mobile): Expo Go would need an exp:// redirect, which the authorization server does not accept. The iOS simulator reaches the API on localhost; for the Android emulator or a phone set EXPO_PUBLIC_API_URL in apps/mobile/.env.local (http://10.0.2.2:8080, or the computer's address on the network).

πŸ“€ Releases

Pushing a tag v<major>.<minor>.<patch> runs the release workflow: it builds the desktop app for macOS (universal), Linux (x86_64 and arm64) and Windows, writes SHA256SUMS, and publishes a GitHub release. Its notes list the feat, fix and perf commits since the previous release (scripts/release-notes.mjs). A tag with a suffix (v1.2.0-beta.1) becomes a prerelease, which the install scripts skip unless you pin it.

git tag -s v0.1.0 -m "Tresso 0.1.0" && git push origin v0.1.0

The version comes from the tag, so nothing needs bumping first. The release also publishes the update feeds that installed apps read from the latest release: appcast.xml for Sparkle and latest.json for Tauri's updater (scripts/update-feeds.mjs, which refuses a release with any update unsigned or signed for another version). The release stays a draft until both feeds are uploaded, so no app sees a release without them. Signing the updates needs three repository secrets: SPARKLE_PRIVATE_KEY (EdDSA, macOS), and TAURI_SIGNING_PRIVATE_KEY with TAURI_SIGNING_PRIVATE_KEY_PASSWORD (Windows, Linux). Their public halves are in apps/desktop/src-tauri/Info.plist and tauri.conf.json. The private keys live in 1Password (vault Employee: "Sparkle update signing key (Tresso)" and "Tauri updater signing key (Tresso)"), and the secrets are set from there, for example op read --no-newline "op://Employee/Sparkle update signing key (Tresso)/password" | gh secret set SPARKLE_PRIVATE_KEY. Never rotate them: installed apps trust only these keys, so a new key means they can never update again. A release talks to Tresso Cloud (https://api.gettresso.com) until the user picks another server. A pull request that changes the release, the desktop app or the install scripts builds every platform without publishing.

πŸ“š Documentation

  • Architecture: components, self-hosted and Tresso Cloud, auth, sync protocol, processing pipeline, scaling
  • Billing: Tresso Cloud's subscriptions, the billing API and Stripe
  • Roadmap: phases from foundation to native apps

πŸ“„ License

Tresso is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0-only): the apps, the server and the shared libraries in packages/.

About

Open source email client: every email is something to do, to hand over, or to know.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages