English
Contributing
This section is for people who change the Mankomail source code: fix a bug, add a node, connect a new service, improve the interface or the documentation. It describes the repository as it is: the packages, the tools, the commands, and the rules that the tooling enforces.
The other pages of the section go deeper:
| Page | When to read it |
|---|---|
| Writing a node | You add or change a node of the catalog. |
| Writing an integration | You connect a third-party service (API key, webhooks, polling trigger). |
| Conventions | Before your first change: TypeScript, package boundaries, i18n, API errors, commits. |
| Testing | You write or run tests, including the ones that need PostgreSQL. |
Repository layout
The repository is a single pnpm workspace driven by Turborepo. The workspace members are every folder under packages/ and apps/ (pnpm-workspace.yaml). All packages are pure ES modules written in TypeScript, published under the @manko/ scope inside the workspace.
| Folder | Package | Role |
|---|---|---|
packages/workflow | @manko/workflow | The isomorphic core: workflow graph and validation, the node contract (defineNode), parameter declarations, the {{ }} templating engine. Depends on no other package of the product. |
packages/api-types | @manko/api-types | The zod schemas shared by the server (validation) and the interface (types): API payloads, error format, connection catalog, integration declarations. |
packages/credentials | @manko/credentials | Credential types, instance encryption, OAuth flows and token refresh. |
packages/connectors | @manko/connectors | Gmail, Microsoft Graph and IMAP/SMTP behind one common contract. |
packages/mirror | @manko/mirror | Mailbox synchronisation: cursors, backfill, push, and the canonical message model. |
packages/nodes | @manko/nodes | The node catalog: each node's declaration and execute function, the service contracts nodes consume, test doubles. |
packages/engine | @manko/engine | The run engine: persisted steps, queue, scheduler, retries, idempotency. |
packages/tools | @manko/tools | The internal tool layer: typed, named capabilities with a declared effect, used by the mailbox analyzer. |
packages/server | @manko/server | The composition point: HTTP and WebSocket API, typed configuration, database access and SQL migrations, integrations. |
packages/ui | @manko/ui | The Vue 3 application: workflow editor, webmail, administration. Built, then served by the server. |
apps/docs | @manko/docs | This documentation (VitePress). Node and integration pages are generated from the real catalog. |
Dependencies between packages only go downwards, and ESLint enforces it. For example, @manko/nodes may import only @manko/workflow, and @manko/ui only @manko/workflow, @manko/api-types and @manko/nodes. The full table is in Conventions.
Prerequisites
| Tool | Version | Where it is pinned |
|---|---|---|
| Node.js | 24 or later | engines in the root package.json; CI runs Node 24. |
| pnpm | 11.24.0 | packageManager in the root package.json. Run corepack enable and Corepack provides the pinned version. |
| Docker | Recent Docker Engine with Compose | Runs PostgreSQL 16 and MinIO for the development stack and the integration tests. |
There is no .nvmrc file: the engines field is the reference. TypeScript, Vitest, ESLint, Biome and Turborepo are development dependencies of the workspace; you do not install them globally.
pnpm install runs no dependency lifecycle scripts: the onlyBuiltDependencies list in pnpm-workspace.yaml is empty on purpose, and CI fails if pnpm reports ignored build scripts. Adding a dependency that needs a build script is an exception to justify in that file.
Set up a development environment
Clone the repository and install the dependencies:
shcorepack enable pnpm installCopy the example configuration. Every variable is documented in it and in Environment variables.
shcp .env.example .envChoose how to run the application.
The full stack in containers.
compose.yamlstarts PostgreSQL 16, MinIO, a job that creates the bucket, and the application built from theDockerfile, on port 3000:shdocker compose up -d curl localhost:3000/healthz # the process is alive curl localhost:3000/readyz # database reachable, migrations appliedThe server and the interface from source, which is what you want while changing code. Start only the dependencies from
compose.yaml, then the two dev servers:shdocker compose up -d postgres minio createbucket pnpm build pnpm --filter @manko/server dev # node --watch, port 3000 pnpm --filter @manko/ui dev # Vite dev serverOpen the URL printed by Vite. The Vite dev server proxies
/api(WebSocket included),/hooks,/healthzand/readyztohttp://localhost:3000, so the session cookie works on a single origin.
Configuration of the server started from source
The server reads its configuration from the process environment only; it does not load .env by itself. Export the variables in the shell that starts it, for example with set -a; . ./.env; set +a.
compose.yaml publishes PostgreSQL on 127.0.0.1:15432 and MinIO on 127.0.0.1:19000, which are not the ports written in .env.example. When you run the server from source against these containers, adjust DATABASE_URL and STORAGE_ENDPOINT accordingly. The credentials of these development containers are in compose.yaml.
On first start with an empty database, the server creates the first administrator from BOOTSTRAP_ADMIN_EMAIL and BOOTSTRAP_ADMIN_PASSWORD when they are set. The setting is ignored as soon as a member exists.
Main commands
Run these from the repository root.
| Command | What it does |
|---|---|
pnpm build | Builds every package with Turborepo (turbo run build), dependencies first. The documentation package is built too, which runs its generator. |
pnpm typecheck | Type-checks every package (turbo run typecheck). |
pnpm lint | Runs ESLint (TypeScript rules, package boundaries, forbidden imports) then biome check (formatting and import order). |
pnpm lint:fix | Same, applying the automatic fixes. |
pnpm format | Reformats the code with Biome. |
pnpm test | Runs Vitest on every project (vitest run). See Testing. |
pnpm test:watch | Vitest in watch mode. |
pnpm dev | Runs the dev script of every package that has one (turbo run dev): server, interface and documentation. |
pnpm clean | Removes build outputs and caches. |
Commands for a single package go through --filter, for example pnpm --filter @manko/docs dev to serve this documentation locally. The documentation commands are detailed in Writing a node.
Before you hand in a change, run at least pnpm typecheck, pnpm lint and the tests of the packages you touched. CI runs the same checks on every push and pull request.
What CI checks
The CI workflow runs one job on every push to main and on every pull request:
pnpm install --frozen-lockfile, and a check that no dependency build script was ignored;pnpm typecheck;pnpm lint;pnpm testagainst a real PostgreSQL 16 and a real MinIO, followed by a guard that fails if any test was skipped or if the number of tests falls below a floor;pnpm build;- a smoke test:
docker compose upof the full stack, then/healthzand/readyzmust answer.
A scheduled job runs the full test suite three times in a row every night to detect unstable tests.
Licence
Check the license field of the package.json of the package you change before contributing. @manko/workflow and @manko/api-types declare Apache-2.0. The other packages declare SEE LICENSE IN LICENSE.md, which refers to a licence file at the root of the repository; that file is authoritative for them.