How to buildOctober 11, 202612 min read
Software architectureStep 6 of 9

Modular monolith or microservices

In short

For most new products a sensible start is a modular monolith: one application and one deployment, but modules with a public API, their own data and an automated boundary check. Microservices give independent deployment and team autonomy, but add network calls, eventual consistency and operational complexity — Martin Fowler calls this the “microservice premium”. Extract a service when a module's boundary has settled and its part of the system has gained a separate team or special requirements for scale, availability or security.

ComparisonLevel: AdvancedModular Monolith & Microservices

After this you will be able to

  • compare a modular monolith and microservices by team, domain, deployment, data and observability and justify the choice for your product
  • list the components of the cost of distribution and the prerequisites before extracting the first service
  • structure modules with a public API, split their data into PostgreSQL schemas and check boundaries with a dependency-cruiser rule in CI
  • plan a gradual strangler fig migration and pick the modules that are candidates for services

Verified with

Node.js 24 LTS (24.18)TypeScript 5.9.3 (tsc --strict), tsc 7.0.2Vitest 5.0.3dependency-cruiser 18.6.0PostgreSQL 17.11 (Docker, postgres:17-alpine)

Checked against docs: October 11, 2026

Contents
  1. 01Three shapes of one system
  2. 02Criteria: team, domain, deployment, data, observability
  3. 03The cost of distribution
  4. 04Data: the hardest boundary to draw
  5. 05A modular monolith in code: public API and no back doors
  6. 06The evolution path: monolith first, then the strangler fig
  7. 07Decision table

Three shapes of one system

A monolith is not an insult. microservices.io defines the monolithic architecture as an application structured as a single deployable component that uses a single database, where all operations are local. Fowler notes that in the microservices world “monolith” usually means only how the application is built — as a single unit — not a poor code structure.

Poor structure is called a Big Ball of Mud, and it is possible in any shape. Fowler writes that in theory nothing stops a monolith from having well-defined module boundaries, but in practice they are too easy to breach. A modular monolith is a monolith where boundaries are not just drawn but protected: each module has a public API, its own data and a CI check that prevents going around the API.

Unit of deployment

Monolith without boundaries
One
Modular monolith
One
Microservices
Many, each with its own pipeline

Calling another part of the system

Monolith without boundaries
Any function, any import
Modular monolith
A function call through the module's public API
Microservices
A network request or a message

Data

Monolith without boundaries
Shared tables, anyone reads anything
Modular monolith
One database, the module owns its tables or schema
Microservices
A service's data is reachable only through its API

A transaction across several parts

Monolith without boundaries
A plain ACID transaction
Modular monolith
Possible, but it couples modules
Microservices
Eventual consistency only: sagas, events

What protects the boundaries

Monolith without boundaries
Nothing
Modular monolith
A dependency linter, database permissions
Microservices
The network and separate repositories

Criteria: team, domain, deployment, data, observability

Team

Modular monolith
One or several teams in one codebase, releases need coordination
Microservices
Small independent teams, each owns its services
Ask yourself
How many teams actually get in each other's way when releasing?

Domain

Modular monolith
Boundaries can be moved with a one-day refactoring
Microservices
Moving responsibility between services is a coordinated API and data migration
Ask yourself
How settled are the domain boundaries?

Deployment

Modular monolith
One artifact, one pipeline; several copies can run behind a load balancer
Microservices
Independent releases, but you need automation, API versioning, compatibility
Ask yourself
Is there automated deploy and rollback for every component?

Data

Modular monolith
ACID transactions across modules, JOINs in reports
Microservices
Database per service: cross-service operations are sagas, queries are API composition or separate read models
Ask yourself
Are there operations that must atomically change data of different parts?

Observability

Modular monolith
One log, one call stack, debugging in one process
Microservices
Distributed tracing, log correlation, metrics per service
Ask yourself
Can you find which service slowed a request down?

Scale and special requirements

Modular monolith
The whole application scales as one
Microservices
A part with special load, availability or security needs runs separately
Ask yourself
Is there a part that needs different resources or a different level of protection?

microservices.io phrases the last criterion as “segregate by characteristics”: resource, availability and security requirements. It is one of the few technical arguments for a separate service; most of the others are organizational.

The main organizational argument is Conway's Law: an organization that designs a system produces a design that copies its communication structure. Fowler retells it like this: if a single team writes a compiler, it will be a one-pass compiler; if the team is divided into two, a two-pass compiler. Microservices let each team look after a relatively independent part of the system with infrequent and formal communication between teams. With a single team this argument does not apply: boundaries between one team's services are just extra network calls.

The cost of distribution

In MicroservicePremium (2015) Fowler calls the cost of microservices a premium: the complexity the style itself adds to a project — automated deployment, monitoring, dealing with failure, eventual consistency. His conclusion: do not consider microservices unless the system is too complex to manage as a monolith, and build most systems as a single monolith, paying attention to modularity within it.

  1. Remote calls are slow

    An in-process function call is almost never the bottleneck; the network can be. If a service calls six others and each of them calls six more, latencies add up. Coarser calls and asynchrony help, but asynchronous code is harder to write and debug.

  2. Remote calls fail

    An in-process call is expected to work; a network request can fail at any moment. For every call you have to decide what to do on failure: a timeout, a retry, degradation, compensation.

  3. Eventual consistency

    Each service owns its data, so a change that spans several services does not become visible at once. The user updates something, refreshes the page — and the change is not there yet.

  4. Operational complexity

    A few applications turn into dozens of services with their own releases. You need rapid provisioning, monitoring and automated deployment — Fowler calls them prerequisites before the move.

  • A new environment comes up in hours, not weeks
  • Monitoring catches both technical failures (errors, availability) and business ones (for example, a drop in orders)
  • Every component deploys and rolls back automatically
  • Logs from different components are linked by one request ID
  • Developers take part in operating their services

The checklist retells what Fowler's MicroservicePrerequisites names as the minimum before the first service, plus log correlation, without which a distributed system is hard to debug. If half the items are missing, the premium will exceed the benefit. More on observability in the monitoring and logging lesson.

Data: the hardest boundary to draw

The Database per service pattern on microservices.io requires a service's data to be private and accessible only via its API, and a service's transactions to involve only its database. That does not need a server per service: the options are private tables, a schema per service or a server per service, and the first two are the cheapest. The pattern's author recommends barriers, such as a separate database user per service with grants only on its own objects — without a barrier developers will bypass the API.

The same trick works in a modular monolith: one PostgreSQL database, a schema per module and a role per module. There are no foreign keys between modules — a booking stores the service ID but validates it through the catalog API. This is a deliberate loss of referential integrity for the sake of module independence; a monolith can keep it, but then extracting the module into a service gets harder.

schema.sql
-- One database, one schema per module
CREATE SCHEMA catalog;
CREATE SCHEMA booking;

CREATE TABLE catalog.services (
  id text PRIMARY KEY,
  title text NOT NULL,
  duration_min integer NOT NULL CHECK (duration_min > 0),
  price_kzt integer NOT NULL,
  archived boolean NOT NULL DEFAULT false
);

-- booking stores the service id, but catalog owns the row:
-- no cross-module foreign key, the id is checked through the catalog API
CREATE TABLE booking.bookings (
  id uuid PRIMARY KEY,
  service_id text NOT NULL,
  master_id text NOT NULL,
  client_phone text NOT NULL,
  starts_at timestamptz NOT NULL,
  ends_at timestamptz NOT NULL
);

-- each module's code connects with its own role and sees only its schema
CREATE ROLE booking_module NOLOGIN;
GRANT USAGE ON SCHEMA booking TO booking_module;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA booking TO booking_module;
A schema per module and a module role with access only to its own schema
check.sql
SET ROLE booking_module;
INSERT INTO booking.bookings VALUES
  (gen_random_uuid(), 'haircut', 'm1', '+77011234567', '2026-10-12 10:00+00', '2026-10-12 11:00+00');
SELECT service_id, master_id FROM booking.bookings;
SELECT title FROM catalog.services;
The check: the booking module role writes to its schema and tries to read the catalog
terminal
$ docker exec -i kb-pg-modulnyj-monolit psql -U postgres < check.sql
SET
INSERT 0 1
 service_id | master_id
------------+-----------
 haircut    | m1
(1 row)

ERROR:  permission denied for schema catalog
LINE 1: SELECT title FROM catalog.services;
                          ^
Real output on PostgreSQL 17: the own schema is accessible, the other one is not

The price of splitting data shows up in operations that span several modules. An example from microservices.io: placing an order must check the customer's credit limit, while customer data and orders belong to different services. In a monolith that is one transaction; between services it is a saga with compensating actions. A query like “customers in a region and their recent orders” is one JOIN in a monolith; between services it is API composition or a separate read model (CQRS). The more such operations, the more expensive the split.

A modular monolith in code: public API and no back doors

A small verified example is an appointment booking service with three modules: the service catalog, booking and notifications. Each module is a folder in src/modules; its public API is the index.ts file, everything else lives in internal and is not available from outside.

src/modules/catalog/index.ts

What is inside
CatalogApi, BookableService, createCatalogModule
Who may import it
Any module

src/modules/catalog/internal/services-store.ts

What is inside
Service storage with price and archive flag
Who may import it
The catalog only

src/modules/booking/index.ts

What is inside
book, BookingError, the BookingCreated event
Who may import it
Any module

src/modules/booking/internal/schedule.ts

What is inside
Interval overlap, booking storage
Who may import it
The booking module only

src/modules/notifications/index.ts

What is inside
Subscription to booking.created
Who may import it
The composition root

src/shared/events.ts

What is inside
In-process event bus
Who may import it
Everyone, but it knows no modules

The catalog exposes only what booking needs: the title and the duration. The price and the archive flag stay inside — they can change without touching other modules.

src/modules/catalog/index.ts
import { type ServiceRow, ServicesStore } from "./internal/services-store.js";

// Public API of the catalog module: the only file other modules may import
export type BookableService = { id: string; title: string; durationMin: number };

export interface CatalogApi {
  findBookableService(id: string): Promise<BookableService | null>;
}

export function createCatalogModule(seed: ServiceRow[] = []) {
  const store = new ServicesStore();
  seed.forEach((row) => store.add(row));

  const api: CatalogApi = {
    async findBookableService(id) {
      const row = store.find(id);
      if (!row || row.archived) return null;
      // price and archive flag stay inside the module
      return { id: row.id, title: row.title, durationMin: row.durationMin };
    },
  };
  return { api, admin: { addService: (row: ServiceRow) => store.add(row) } };
}
The catalog's public API: a narrow interface instead of access to its storage

The booking module depends on the CatalogApi interface, not on the catalog's implementation, and announces a new booking with an event. Within one process a simple bus delivers the event; if the notifications module becomes a service, a message broker takes its place, and the transactional outbox pattern makes publishing reliable — it is covered in the lesson on events and queues.

src/modules/booking/index.ts
import { randomUUID } from "node:crypto";
import type { InProcessEvents } from "../../shared/events.js";
import type { CatalogApi } from "../catalog/index.js";
import { BookingsStore, overlaps } from "./internal/schedule.js";

// Public API of the booking module: its input, its errors and its events
export type BookInput = { serviceId: string; masterId: string; clientPhone: string; startsAt: Date };
export type BookingCreated = {
  type: "booking.created";
  bookingId: string;
  masterId: string;
  clientPhone: string;
  serviceTitle: string;
  startsAt: string;
};

export class BookingError extends Error {
  constructor(readonly code: "UNKNOWN_SERVICE" | "SLOT_TAKEN") {
    super(code);
  }
}

export function createBookingModule(deps: {
  catalog: CatalogApi;
  events: InProcessEvents<BookingCreated>;
}) {
  const store = new BookingsStore();

  return {
    async book(input: BookInput): Promise<{ bookingId: string; endsAt: Date }> {
      // Another module is reached only through its public API
      const service = await deps.catalog.findBookableService(input.serviceId);
      if (!service) throw new BookingError("UNKNOWN_SERVICE");

      const endsAt = new Date(input.startsAt.getTime() + service.durationMin * 60_000);
      const slot = { startsAt: input.startsAt, endsAt };
      if (store.forMaster(input.masterId).some((b) => overlaps(b, slot))) {
        throw new BookingError("SLOT_TAKEN");
      }

      const booking = { id: randomUUID(), ...input, endsAt };
      store.add(booking);
      await deps.events.publish({
        type: "booking.created",
        bookingId: booking.id,
        masterId: booking.masterId,
        clientPhone: booking.clientPhone,
        serviceTitle: service.title,
        startsAt: booking.startsAt.toISOString(),
      });
      return { bookingId: booking.id, endsAt };
    },
  };
}
The booking module: the “a master cannot be booked twice” rule, a catalog call through its API and an event
src/shared/events.ts
// In-process event bus: modules talk without importing each other's internals.
// If a module becomes a service, a message broker takes this place.
type Handler<E> = (event: E) => void | Promise<void>;

export class InProcessEvents<E extends { type: string }> {
  private readonly handlers = new Map<string, Handler<E>[]>();

  subscribe<T extends E["type"]>(type: T, handler: Handler<Extract<E, { type: T }>>): void {
    const list = this.handlers.get(type) ?? [];
    list.push(handler as Handler<E>);
    this.handlers.set(type, list);
  }

  async publish(event: E): Promise<void> {
    for (const handler of this.handlers.get(event.type) ?? []) {
      await handler(event);
    }
  }
}
In-process event bus: a typed subscription by the type field
src/modules/notifications/index.ts
import type { InProcessEvents } from "../../shared/events.js";
import type { BookingCreated } from "../booking/index.js";

// Notifications know the booking event contract, not the booking tables
export function createNotificationsModule(deps: { events: InProcessEvents<BookingCreated> }) {
  const outbox: string[] = [];

  deps.events.subscribe("booking.created", (event) => {
    outbox.push(`${event.clientPhone}: ${event.serviceTitle}, ${event.startsAt}`);
  });

  return { sent: () => [...outbox] };
}
The notifications module knows the event contract, not the booking tables
src/app.ts
import { createBookingModule, type BookingCreated } from "./modules/booking/index.js";
import { createCatalogModule } from "./modules/catalog/index.js";
import { createNotificationsModule } from "./modules/notifications/index.js";
import { InProcessEvents } from "./shared/events.js";

// One process, one deployment: modules are wired together here
export function createApp() {
  const events = new InProcessEvents<BookingCreated>();
  const catalog = createCatalogModule([
    { id: "haircut", title: "Haircut", durationMin: 60, priceKzt: 8000, archived: false },
    { id: "beard", title: "Beard trim", durationMin: 30, priceKzt: 4000, archived: true },
  ]);
  const booking = createBookingModule({ catalog: catalog.api, events });
  const notifications = createNotificationsModule({ events });
  return { catalog, booking, notifications };
}
The composition root: one process, one deployment, modules are wired here
test/app.test.ts
import { describe, expect, it } from "vitest";
import { createApp } from "../src/app.js";
import { BookingError } from "../src/modules/booking/index.js";

const at = (iso: string) => new Date(iso);

describe("modular monolith: booking through public APIs", () => {
  it("books a slot and notifies through an in-process event", async () => {
    const app = createApp();
    const { endsAt } = await app.booking.book({
      serviceId: "haircut",
      masterId: "m1",
      clientPhone: "+77011234567",
      startsAt: at("2026-10-12T10:00:00Z"),
    });

    expect(endsAt).toEqual(at("2026-10-12T11:00:00Z"));
    expect(app.notifications.sent()).toEqual([
      "+77011234567: Haircut, 2026-10-12T10:00:00.000Z",
    ]);
  });

  it("rejects an overlapping slot for the same master", async () => {
    const app = createApp();
    const base = { serviceId: "haircut", masterId: "m1", clientPhone: "+77011234567" };
    await app.booking.book({ ...base, startsAt: at("2026-10-12T10:00:00Z") });

    await expect(
      app.booking.book({ ...base, startsAt: at("2026-10-12T10:30:00Z") }),
    ).rejects.toEqual(new BookingError("SLOT_TAKEN"));
    // back-to-back is fine: intervals are half-open
    await app.booking.book({ ...base, startsAt: at("2026-10-12T11:00:00Z") });
    expect(app.notifications.sent()).toHaveLength(2);
  });

  it("does not book archived or unknown services", async () => {
    const app = createApp();
    const input = { masterId: "m1", clientPhone: "+77011234567", startsAt: at("2026-10-12T10:00:00Z") };

    await expect(app.booking.book({ ...input, serviceId: "beard" })).rejects.toMatchObject({
      code: "UNKNOWN_SERVICE",
    });
    await expect(app.booking.book({ ...input, serviceId: "nope" })).rejects.toBeInstanceOf(BookingError);
  });
});
Tests go only through the modules' public APIs

The compiler does not forbid importing ../catalog/internal/… from the booking module. dependency-cruiser does: a rule with a $1 group lets a module import its own files and other modules' index.ts, but not their internals, and no-circular requires module dependencies to form an acyclic graph.

.dependency-cruiser.cjs
/** @type {import('dependency-cruiser').IConfiguration} */
module.exports = {
  forbidden: [
    {
      name: "modules-only-via-public-api",
      comment: "Another module is reachable only through its index.ts",
      severity: "error",
      from: { path: "^src/modules/([^/]+)/" },
      to: {
        path: "^src/modules/",
        pathNot: ["^src/modules/$1/", "^src/modules/[^/]+/index\\.ts$"],
      },
    },
    {
      name: "shared-knows-no-modules",
      comment: "shared is a toolbox, not a back door between modules",
      severity: "error",
      from: { path: "^src/shared/" },
      to: { path: "^src/modules/" },
    },
    {
      name: "no-circular",
      comment: "Module dependencies must form a directed acyclic graph",
      severity: "error",
      from: {},
      to: { circular: true },
    },
    {
      name: "not-to-unresolvable",
      severity: "error",
      from: {},
      to: { couldNotResolve: true },
    },
  ],
  options: {
    doNotFollow: { path: "node_modules" },
    tsPreCompilationDeps: true,
    tsConfig: { fileName: "tsconfig.json" },
  },
};
Module boundaries as rules: public API only, shared knows no modules, no cycles
terminal
$ npx tsc --noEmit && npx vitest run --reporter=verbose

 ✓ test/app.test.ts > modular monolith: booking through public APIs > books a slot and notifies through an in-process event 3ms
 ✓ test/app.test.ts > modular monolith: booking through public APIs > rejects an overlapping slot for the same master 1ms
 ✓ test/app.test.ts > modular monolith: booking through public APIs > does not book archived or unknown services 1ms

 Test Files  1 passed (1)
      Tests  3 passed (3)

$ npx depcruise src --config .dependency-cruiser.cjs

✔ no dependency violations found (8 modules, 11 dependencies cruised)
Real output (trimmed): types, tests and boundaries are fine

Now two typical violations: the booking module borrows the price type from the catalog's internals, and the catalog starts importing the booking module to count popular services.

diff
--- a/src/modules/booking/index.ts
+++ b/src/modules/booking/index.ts
@@ -1,6 +1,7 @@
 import { randomUUID } from "node:crypto";
 import type { InProcessEvents } from "../../shared/events.js";
 import type { CatalogApi } from "../catalog/index.js";
+import type { ServiceRow } from "../catalog/internal/services-store.js";
 import { BookingsStore, overlaps } from "./internal/schedule.js";
 
 // Public API of the booking module: its input, its errors and its events
@@ -12,6 +13,7 @@ export type BookingCreated = {
   clientPhone: string;
   serviceTitle: string;
   startsAt: string;
+  priceKzt?: ServiceRow["priceKzt"];
 };
 
 export class BookingError extends Error {
--- a/src/modules/catalog/index.ts
+++ b/src/modules/catalog/index.ts
@@ -1,5 +1,9 @@
+import type { BookingCreated } from "../booking/index.js";
 import { type ServiceRow, ServicesStore } from "./internal/services-store.js";
 
+// catalog wants to count popular services from booking events
+export type PopularityInput = Pick<BookingCreated, "serviceTitle">;
+
 // Public API of the catalog module: the only file other modules may import
 export type BookableService = { id: string; title: string; durationMin: number };
Violations that the compiler and the tests let through
terminal
$ npx tsc --noEmit && npx vitest run
      Tests  3 passed (3)
$ npx depcruise src --config .dependency-cruiser.cjs

  error no-circular: src/modules/booking/index.ts →
      src/modules/catalog/index.ts →
      src/modules/booking/index.ts
  error modules-only-via-public-api: src/modules/booking/index.ts → src/modules/catalog/internal/services-store.ts

x 2 dependency violations (2 errors, 0 warnings). 8 modules, 13 dependencies cruised.
Real output (vitest output trimmed): tests are green, the boundary check is not

The proper fixes: if booking needs the price, the catalog adds it to the public BookableService; if the catalog needs statistics, it subscribes to the booking.created event in the composition root instead of importing the booking module. In Java, Spring Modulith gives the same check: calling ApplicationModules.of(Application.class).verify() rejects cycles between modules and references to their internal packages.

The evolution path: monolith first, then the strangler fig

In MonolithFirst (2015) Fowler writes that almost all successful microservice stories started with a monolith that got too big and was broken up, while almost all systems built as microservices from scratch ended up in serious trouble. Two arguments: at the start you do not know whether users need the product, and speed matters more than scalability; and the right boundaries between services are hard to get right even for experienced architects in familiar domains — in a monolith they are cheaper to find and move. Fowler also gives the counter-argument: building a sufficiently modular monolith takes discipline, and an early split gets teams used to working independently.

When extraction is due, Fowler advises against rewriting the system wholesale and in favour of replacing it gradually — the Strangler Fig approach. The name comes from the strangler fig that grows on a host tree and eventually replaces it. New features and extracted parts are built next to the old system, behaviour moves over piece by piece, and the temporary code that lets old and new coexist (transitional architecture) is removed at the end. Fowler names four activities: understand the outcomes you want, decide how to break the problem into parts, successfully deliver the parts, and change the organization so this happens on an ongoing basis.

Modules with a public APIData split by schemasCommunication via API and eventsModule becomes a serviceOld code removed
inside the monolithstrangler fig
The evolution path: first boundaries inside one deployment, then moving one module at a time

A modular monolith shortens this path: the module's public API is already a contract, the data is already split by schemas, communication already goes through events. Extraction becomes replacing a function call with a network client and the in-process bus with a broker, not digging through shared code. Fowler also mentions an intermediate route — start with a few coarse-grained services and split them once boundaries settle.

Service catalog

Owns data
Services, prices, durations
Candidate for a service?
No: few rules, read by everyone, changes together with booking

Booking

Owns data
Bookings, master schedules
Candidate for a service?
Not while there is one team: it is the core and needs transactions

Notifications

Owns data
Templates, delivery log
Candidate for a service?
First candidate: asynchronous, external providers, its own load profile

Payments

Owns data
Payments, refunds
Candidate for a service?
Possibly: different security and audit requirements

Reports

Owns data
Read model
Candidate for a service?
Possibly: heavy queries must not slow booking down

The table is a teaching map of modules, not a universal answer: the same notifications module can live in the monolith for years if there are few deliveries and one team.

Decision table

A new product, the hypothesis is untested

What to choose
Modular monolith
Why
Speed of change matters more than scale; boundaries will still move

One team of up to 8–10 people

What to choose
Modular monolith
Why
Conway's argument does not apply, the distribution premium does not pay off

Several teams get in each other's way when releasing

What to choose
A modular monolith with strict boundaries, then extracting one service at a time
Why
First check that deployment is the problem, not code coupling

Part of the system needs different load, availability or protection

What to choose
Extract that part into a service
Why
Segregating by characteristics is a technical argument for a service

A legacy monolith without boundaries that has to move

What to choose
Strangler fig: new features alongside, migration piece by piece
Why
A full replacement delivers no value for a long time and rarely keeps up with the business

No automated deploy, monitoring or tracing

What to choose
Do not start with microservices
Why
The prerequisites are missing; the premium will exceed the benefit

The “8–10 people” threshold is our rule of thumb from experience, not a rule from the sources: what matters is not headcount but how many teams independently ship changes into one codebase.

Next: the clean architecture lesson shows how to arrange layers inside one module, the lesson on events and queues shows how to deliver events reliably between modules and services, and the system design walkthrough applies these decisions to a concrete problem end to end.

How this was verified4 · passedchecked 11 October 2026

“Run” means the code was executed in a scratch project or container and the output in the article comes from that run. “Partial” means it was checked against stubs instead of external services. “Not run” means it needs a device, an account or a key; the steps were checked against the official documentation. The verification project files and run logs are kept in the InterLAB archive.

Tell us about your task — we'll review your request and get back to you

Got a project?
We'll scope and estimate it — for free.

Tell us about the product or the problem. After reviewing the task we'll come back with an architecture, a timeline and a price for the first version.

  • Scope and risks estimated before we start
  • NDA — yours or ours
  • Work starts within a week

Please do not send passwords, access keys or source code through this form — we will agree a safe way to share access separately.

By clicking the button you agree to the processing of your personal data.