Turn mailboxes into issue boards.
Multi-tenant application that syncs project mailboxes (IMAP/SMTP) with issue trackers. Supported providers: GitLab (cloud + self-hosted) and GitHub (cloud + Enterprise Server).
| Layer | Technology |
|---|---|
| API | Bun + Hono |
| Worker | Bun + pg-boss |
| Frontend | Vite + React + Base UI + CSS Modules |
| Database | PostgreSQL + Drizzle ORM |
| Deploy | Docker Compose or Helm + Docker |
Inbound (email → issue)
Message-ID / References / subject+senderOutbound (comment → email)
note_events on GitLab, issue_comment on GitHub) or polling fallback[internal] marker at the start or end, or GitLab internal notes), email-synced comments (sync marker), bot-authored notes, and already-processed notesNew issues include a collapsible Support details footer with a link back to the ServiceBeard project and a reminder about the [internal] marker.
cp .env.example .env # Edit .env — see Configuration below (at minimum DATABASE_URL, ENCRYPTION_KEY, SESSION_SECRET, and login providers)
bun install
docker compose up -d
This starts Postgres, GreenMail, Roundcube, and Adminer (local development only).
bun run db:generate # after schema changes only bun run db:migrate
db:migrate applies OSS migrations. When SB_EXTENSION_MANIFEST is set in .env, extension migrations run in the same command.
bun run dev
This runs the API, worker, and web UI in parallel via Bun workspaces (bun run --filter './apps/*' dev).
Run a single app:
bun run dev:api bun run dev:worker bun run dev:web
| Service | URL |
|---|---|
| Web UI | http://localhost:5173 |
| API | http://localhost:3000 |
| Dev mail UI (Roundcube) | http://localhost:8888 |
| GreenMail API / raw browser | http://localhost:8080 |
| Database UI (Adminer) | http://localhost:8081 |
Dev login (when LOCAL_LOGIN=true): dev@localhost / dev
Environment variables for local development live in .env.example. Production Docker Compose uses deploy/compose/.env.example. Kubernetes deployments use Helm values (see Deploy).
| Variable | Description |
|---|---|
DATABASE_URL |
Postgres connection string |
ENCRYPTION_KEY |
64-char hex key for AES-256-GCM secret encryption |
SESSION_SECRET |
Session signing secret |
NODE_ENV |
development or production |
PORT |
API listen port (default: 3000) |
LOG_LEVEL |
Log verbosity (debug, info, warn, error; default: info) |
| Variable | Description |
|---|---|
API_URL |
Public API base URL (e.g. http://localhost:3000) |
WEB_URL |
Public web UI base URL (e.g. http://localhost:5173) |
WEBHOOK_BASE_URL |
Public URL for issue-tracker webhooks (GitLab/GitHub must reach this) |
OAUTH_REDIRECT_URI |
Optional override for OAuth callback (default: {WEB_URL}/api/auth/callback) |
TLS_CA_BUNDLE |
Optional path to a PEM CA bundle used for all provider API calls (merged with per-project CA) |
| Variable | Description |
|---|---|
IMAP_POLL_INTERVAL_SECONDS |
IMAP poll interval in seconds (minimum: 60) |
COMMENT_POLL_INTERVAL_SECONDS |
Comment poll interval in seconds (minimum: 60) |
SEND_EMAIL_CONCURRENCY |
Parallel outbound email workers (default: 5) |
IMAP_POLL_CONCURRENCY |
Parallel per-project IMAP poll workers (default: 3) |
COMMENT_POLL_CONCURRENCY |
Parallel per-project comment poll workers (default: 3) |
SMTP_MAX_CONNECTIONS |
Max SMTP connections per credential pool (default: 3) |
SMTP_MAX_MESSAGES |
Max messages per pooled SMTP connection (default: 100) |
SMTP_IDLE_TTL_MS |
Evict idle SMTP transporter pools after ms (default: 60000) |
SMTP_MAX_POOLS |
LRU cap on cached SMTP credential pools (default: 50) |
Servicebeard supports multiple login providers. Enable one or more by setting *_LOGIN=true and the required credentials (see .env.example). All OAuth/OIDC providers share one callback URL, defaulting to {WEB_URL}/api/auth/callback so the OAuth state cookie matches the browser origin (in local dev, register http://localhost:5173/api/auth/callback in your OAuth app). Override with OAUTH_REDIRECT_URI if needed.
Enabled with LOCAL_LOGIN=true. Supports credential sign-in and passkeys without an external IdP. In development, the API also seeds dev@localhost / dev on startup for quick testing.
| Variable | Description |
|---|---|
LOCAL_LOGIN |
true to enable, false or unset to disable |
LOCAL_LOGIN_SIGNUP |
Allow sign-up via local credentials (default: true when local login is enabled) |
WEBAUTHN_RP_ID |
Passkey relying party ID (defaults from WEB_URL) |
WEBAUTHN_RP_NAME |
Passkey relying party display name (default: Servicebeard) |
WEBAUTHN_ORIGIN |
Passkey origin (defaults from WEB_URL) |
Works with any OpenID Connect provider (Keycloak, Auth0, etc.).
| Variable | Description |
|---|---|
OIDC_LOGIN |
true to enable (requires OIDC_* config), false or unset to disable |
OIDC_SIGNUP |
Allow new users on first sign-in (default: true) |
OIDC_PROVIDER_NAME |
Display name on the login button (e.g. Keycloak → "Continue with Keycloak"; omit for "Continue with SSO") |
OIDC_ISSUER |
Issuer URL (e.g. https://auth.example.com/realms/myrealm) |
OIDC_CLIENT_ID |
Client ID |
OIDC_CLIENT_SECRET |
Client secret |
Register {WEB_URL}/api/auth/callback as the redirect URI in your IdP.
{WEB_URL}/api/auth/callback (local dev: http://localhost:5173/api/auth/callback).env:| Variable | Description |
|---|---|
GITHUB_LOGIN |
true to enable (requires GITHUB_* config), false or unset to disable |
GITHUB_SIGNUP |
Allow new users on first sign-in (default: true) |
GITHUB_CLIENT_ID |
OAuth App client ID |
GITHUB_CLIENT_SECRET |
OAuth App client secret |
Optional alternative to per-project personal access tokens. When configured, projects can authenticate with a GitHub App installation ID instead of a PAT. Issues and comments appear as your-app[bot].
{API_URL}/api/github-app/setup (for local dev: http://localhost:3000/api/github-app/setup)..env for the API and worker:| Variable | Description |
|---|---|
GITHUB_APP_ID |
Numeric App ID from the app settings page |
GITHUB_APP_PRIVATE_KEY |
PEM private key (use \n for line breaks in .env), or |
GITHUB_APP_PRIVATE_KEY_PATH |
Path to the .pem file (relative to repo root, or absolute) |
GITHUB_APP_ID is not set, projects use a personal access token instead.PAT alternative: create a dedicated machine/bot GitHub account, grant it access to the repository, and generate a fine-grained or classic PAT for that account — not your personal login — so created issues clearly come from ServiceBeard.
Works with GitLab.com or self-hosted GitLab.
{WEB_URL}/api/auth/callback (local dev: http://localhost:5173/api/auth/callback)read_user scope.env:| Variable | Description |
|---|---|
GITLAB_LOGIN |
true to enable (requires GITLAB_* config), false or unset to disable |
GITLAB_SIGNUP |
Allow new users on first sign-in (default: true) |
GITLAB_BASE_URL |
GitLab instance URL (default: https://gitlab.com) |
GITLAB_CLIENT_ID |
Application ID |
GITLAB_CLIENT_SECRET |
Application secret |
{WEB_URL}/api/auth/callback (local dev: http://localhost:5173/api/auth/callback)read scope (included by default).env:| Variable | Description |
|---|---|
LINEAR_LOGIN |
true to enable (requires LINEAR_* config), false or unset to disable |
LINEAR_SIGNUP |
Allow new users on first sign-in (default: true) |
LINEAR_CLIENT_ID |
OAuth application client ID |
LINEAR_CLIENT_SECRET |
OAuth application client secret |
For production, register {WEB_URL}/api/auth/callback in each OAuth app (same host users sign in from).
Used for team invites, password reset, and email verification when local login is enabled.