Files
pl_mail/CONTRIBUTING.md
T
2026-07-28 22:25:00 +02:00

5.0 KiB

Contributing to plMail

Development setup, test suites and console reference. For installing and running plMail, see the README.

Stack

Layer Technology
Backend Symfony 8, Doctrine ORM, PHP 8.4+
Database PostgreSQL 18
IMAP webklex/php-imap
Gmail Gmail REST + Batch API, OAuth2 (league/oauth2-client)
Microsoft Microsoft Graph API, OAuth2
Async Symfony Messenger, Doctrine transport
Push Mercure hub, Gmail Cloud Pub/Sub watch, Graph subscriptions, Web Push (VAPID)
Interop JMAP server (/jmap), authenticated with per-user app passwords
Frontend AssetMapper, Tailwind v4, Hotwire Turbo, Stimulus
Runtime FrankenPHP
Credentials libsodium secretbox via the encrypted_string Doctrine type
Dev tooling Docker Compose, Adminer, Mailpit

Development setup

cp .env .env.local
docker compose up --build

Migrations run automatically via the entrypoint. Create the first user with app:setup, or register through the UI. The imap-supervisor, messenger-worker and scheduler services start with the stack and restart on failure.

Tests

Two suites: PHPUnit for unit tests (tests/, mirroring src/) and Playwright for browser end-to-end tests (tests/e2e/).

Both run against compose.test.yaml — a separate compose project with its own Postgres, so they never touch the dev stack or the database holding your mail. Playwright runs on the host (Node 18+; the repo ships an .nvmrc).

npm ci
npx playwright install chromium
npm run test:unit:docker
npm run test:e2e:docker
Command Description
npm run test:e2e:docker:ui Playwright's watch UI
npm run test:env:up Start the test stack (migrates, builds assets, seeds the E2E user)
npm run test:env:down Stop it, keeping the database volume
npm run test:env:reset Stop it and delete its volumes — next run rebuilds from scratch
npm run test:env:logs Tail the test app's logs

The test app is served at http://127.0.0.1:8001 (override with TEST_HTTP_PORT). Individual specs reseed their own fixtures, so tests are independent and re-runnable.

CI runs the same suites without Docker — see .github/workflows/e2e.yml.

README screenshots

tests/e2e/screenshots.spec.ts regenerates the images in docs/screenshots/. It asserts nothing and is not part of the regression suite — run it deliberately, against a stack seeded with demo data:

E2E_DOCKER=1 npx playwright test screenshots.spec.ts --project=chromium --workers=1

Never point it at a stack holding real mail.

Console commands

Command Description
app:setup Create the first admin user (interactive)
app:mail:sync [account-id] Dispatch an account-level sync for one or all active accounts
app:mail:send-draft [message-id] Send a draft message (picker if no ID given)
app:mail:test-connection Probe an account's IMAP/SMTP settings
app:contacts:harvest [account-id] Harvest contact addresses from synced messages
app:label:backfill [--account=ID] Create labels from existing mailboxes and backfill assignments
app:imap:idle <mailbox-id> Hold an IMAP IDLE connection for a single mailbox
app:imap:supervise Spawn and watch one app:imap:idle process per IDLE-enabled mailbox
app:imap:test [--account=ID] Test an IMAP connection and folder listing
app:push:renew Renew Gmail watches and Graph subscriptions nearing expiry
app:push:generate-vapid-keys Generate a VAPID keypair for Web Push
app:graph:diagnose Probe Microsoft Graph access for one account and report what works
app:attachments:reclassify Recompute inline/attachment classification for stored parts
app:prune:blobs [--days=N] [--dry-run] Expire staged JMAP uploads and delete files orphaned by deleted rows
app:user:promote <email> [--revoke] Grant or revoke ROLE_ADMIN
app:monitoring:prune [--days=N] Prune old log entries and dead process heartbeats
app:reset Truncate synced data — useful during development

These run on a schedule already — see App\Infrastructure\Scheduler\MaintenanceSchedule for the cadences (polling sync every 15 min, push renewal and monitoring pruning nightly, the blob sweep weekly). They are dispatched by the scheduler service in compose, which consumes the scheduler_default transport; without that container running, none of them fire. php bin/console debug:scheduler shows the next run of each.

Roadmap

  • Complete the label-based architecture refactor (Label as the user-facing concept; Mailbox demoted to IMAP sync infrastructure)
  • Sanitize rendered HTML bodies
  • Full-text search
  • Microsoft OAuth2 / Graph send support
  • Gmail-native threadId threading (currently RFC Message-ID based)
  • Incoming IMAP flag sync over the IDLE stream
  • Avatar fetching (once OAuth avatar scopes are wired)
  • Nested label UI