Scaling Mautic: How We Achieved Multi-Tenancy (Single Code, Multi-DB)

Hi everyone,

I wanted to share how we implemented multi-tenancy in Mautic using a single codebase with multiple database instances. The goal was to make it highly scalable, flexible, and production-ready for multiple clients.

This approach worked really well for us in real-world uses. It also helped us turn Mautic into a SaaS-like platform, where a single instance can serve multiple clients without things getting messy.

High-Level Architecture

We designed the system as follows:

  • One Core Database
  • Multiple Tenant Databases (one per tenant)

Core DB handles:

  • Users
  • Authentication
  • Tenant registry
  • Mapping users ↔ tenants

Tenant DB handles:

  • Contacts
  • Segments
  • Campaigns
  • Emails
  • Plugins
  • Permissions
  • Everything Mautic-related for that tenant

Architecture Flowchart

The idea is simple: one instance serving many isolated databases.

  • Core Database: Handles the Core data like user authentication, tenants data, and a mapping table (tenant_users) that defines who has access to which tenant.

  • Tenant Databases: Each project/tenant has their own completely isolated database.

  • Dynamic Configuration: Instead of a single local.php, we store tenant-specific configs in app/config/tenant/{tenant_id}.php

Authentication Flow

Step-by-step:

  1. User sends login request
  2. Core DB validates credentials
  3. On success:
    • Check if tenant cookie exists

      • YES → connect to tenant DB dynamically
      • NO → redirect to tenant selection page
  4. Once tenant is resolved:
    • Load tenant DB

    • Validate user permissions from tenant DB

  5. Login successful

Request Lifecycle Flow

The tenant DB connection stays consistent throughout the request lifecycle.

Public & API URLs

Since tracking pixels, landing pages, and API calls don’t use cookies, we updated the URL pattern: mautic-url.com/t/{tenant_id}/{original-path} The middleware extracts the {tenant_id} directly from the URL to route the data to the correct DB instantly.

Switching Between Tenants

We added a simple UX improvement

  • Dropdown in top navigation
  • Shows all accessible tenants
  • On selection:
    • Updates tenant cookie
    • Reloads context

Command Handling (CLI)

We extended Mautic commands with an optional parameter. --tenant-id

  • If tenant ID is provided → runs for that tenant
  • If not provided → runs for all tenants

Example

php bin/console mautic:emails:send --tenant-id=1

This makes cron jobs super flexible.

Installation

We modified the installation steps. During setup, you specify Core DB credentials and your first Tenant DB. You can choose to host the DB on the same server or point to a remote DB server.

Tenant Management

There is a dedicated Manage Tenants page. Creating a new client is a 10-second process. System creates the DB, creates the schema, and generates the config file automatically.

Video link: https://drive.google.com/file/d/1w3KPZZmz0J8pS1s77-y77RQn3Ti4ZCwr/view?usp=sharing

New Configuration Parameters

We also introduced a couple of configuration options to make things more controlled and flexible:

'max_tenants_limit' => 10,
'allow_remote_db_for_tenants' => true,
  • max_tenants_limit → This helps restrict how many tenants can be created in a single instance. Useful to avoid overloading the system or to control usage based on your setup.

  • allow_remote_db_for_tenants → When enabled, it allows tenant databases to be hosted on remote servers.

Why This is Highly Scalable?

  1. Database Decoupling: You can host the application on Server A and the tenant database on Server B (with SSL support). This is crucial for clients with strict data residency requirements.

  2. Custom Domains: Supports domain mapping. Client A can use marketing.clientA.com while Client B uses mautic.clientB.com. The middleware validates the domain against the Core DB to ensure the request hits the right data.

  3. Easy Horizontal Scaling: Add new tenants without affecting others. No shared DB bottleneck

  4. Tenant Isolation: Each tenant has its own email settings, its own plugins, and its own user roles. They never even know other tenants exist on the same server.

  5. Compliance Friendly: Clients can host their own data. Supports secure connections (SSL)

  6. Single Codebase Maintenance

    • One deployment
    • One upgrade process
    • Multiple tenants benefit instantly

Would love to hear more ideas or suggestions from the community on how we can make this even more robust. Always open to feedback and improvements :+1:

@vwesley aren’t the cronjobs the bottleneck? Could you tell us more about your setup, e.g. are you running K8?

@dband That’s correct.
Have few plans to implement Redis/RabbitMQ having seperate worker for each tenant job.

At this moment, we are using docker setup. Would like to explore more about EKS or others

That’s the right direction. The problems you need to solve are similar to those in SaaS apps. Resource monitoring, limiting, etc. per client.

It’s a cool idea but will be a struggle to keep in sync with core changes.