-
Notifications
You must be signed in to change notification settings - Fork 15
Multi Tenancy Progress
Part of the Multi-tenancy section. Planned / in active development. Tracks the build against the plan. Last updated: 2026-07-05.
Legend: β done Β Β·Β π§ in progress / next Β Β·Β β¬ not started
Where things stand: the tickets module is now solid for real multi-company use. The foundation is complete; tickets scope by company; per-company email routing works end-to-end (pinned/shared mailboxes, sender-domain and individual-address routing, a triage safety net, and diagnostics); the first per-company settings are live (ticket types & origins); the whole ticket layer is fully isolated (every ticket read/write is access-checked β see Data isolation & the audit); and you can now control which companies each analyst may access. What's left is rolling per-company scoping through the other modules, plus per-company departments/SLAs and the self-service portal.
The invisible plumbing. None of this changes anything on a single-company install.
- β
tenantstable + a silent Default company that owns all existing data - β
tenant_domainstable (for future email-domain routing) - β Analyst β company access model (which staff can see which companies, + an "all companies" flag) β now with an admin UI to grant/deny it per analyst (System β Analysts; see Staff access)
- β
Team β company access β a team can grant its members access to specific companies (or all), additive to each analyst's own access, set on System β Teams β Manage companies. Enforced through the same
getAccessibleTenantIds()choke-point; defaults to granting nothing so upgrades never widen access. System β Analysts shows effective (direct + team-inherited) access. New tableteam_tenant_access+teams.can_access_all_tenants. - β
tenant_idon tickets (every existing ticket assigned to the Default company) - β Shared tenancy helper β the single choke-point all tenant logic routes through
- β Active-company session context
- β Company switcher in the header (appears only once a 2nd company exists)
- β System β Companies admin page (list / create / rename companies)
Making the tickets module company-aware.
- β Ticket list scopes to the active company
- β New tickets are stamped with the active company
- β Folder count badges (departments / statuses / analysts / unassigned) scope to the active company
- β Full ticket-layer isolation (Phase 1.5) β every ticket endpoint (detail, thread, attachments, notes, time, audit, CMDB, SLA, CSAT, and every action) is now access-checked, verified by a three-sweep audit. The tickets module is safe for real multi-company use.
So an MSP can receive each client's email and have it land under the right company.
- β Link mailboxes to companies (pinned = one mailbox per company, shared intake = one inbox sorted by sender) β set in Tickets β Settings β Mailboxes
- β Register each company's email domains β managed in System β Companies
- β Route incoming email to the right company by sender domain (covers both Microsoft & Gmail import)
- β
Specific senders β map an individual address to a company (e.g.
jane@gmail.comβ Acme), matched before the domain, so personal/webmail senders route correctly - β Triage queue for unmatched / unknown senders β reached from the company switcher, with one-click filing and "route this domain" or "always file this sender" (each sweeps the other queued mail and auto-routes future mail)
- β Public-email (Gmail/Outlook/β¦) domains are never auto-mapped (different clients share them); the built-in list is customisable (add-only β extend it, can't un-protect a built-in)
- β "How email reaches this company" β a read-only routing summary on each company's editor
- β Email routing test β a dry-run diagnostic: type a sender + mailbox, see where it would land and why
- β¬ A per-company reply-identity override (replies already go out from the mailbox the ticket arrived on; this is the only outbound piece left) (next up)
Routing rule (first match wins): reply to an existing ticket β inherits its company; otherwise a pinned mailbox β its company (sender ignored); otherwise a shared mailbox β matched by the exact sender address, then the sender domain β that company, or triage if nothing matches. On a single-company install this is invisible β everything lands under the one Default company exactly as before.
- π§ Per-module Settings: global default + per-company override (the add + hide model). Built: ticket types, ticket origins. Next: categories, departments (with extra wiring), SLAs. Statuses/priorities/teams/mailboxes stay global on purpose β see Settings rationale.
- β
Change Management β company-scoped like tickets/problems (list + calendar scope to the active company; new changes stamped with it; every by-id read/write, UI and REST API, isolated by company; a change in one company is a 404 to another).
changes.tenant_id, NULL = Default; invisible at N=1. - β
Asset Management β assets belong to a company (
assets.tenant_id); the list and dashboard scope to it, agent ingest files machines by their API key's company (apikeys.tenant_id, so two clients can each have aLAPTOP-01), and every by-id read/write (incl. an asset's disks/software/history child reads), UI and REST API, is isolated. Asset types, statuses are per-company add/hide lists, and locations are a purely per-company tree (a client's sites are entirely its own). Suppliers stay a shared registry (used by Contracts/RFP too β changeable later). Deferred: routing vCenter/Intune-synced machines to their company (they land in Default for now). See the Developer Guide β assets is its worked example. - β
Knowledge β articles have a company and an audience (internal / customer / public), so a web chat widget can only ever be answered from its own company's articles plus shared ones, and only from articles deliberately marked public. Note this module intentionally breaks the usual
NULLrule β blank means shared with everyone, not "the Default company's" β because an MSP's generic how-to legitimately serves every client. - β
Messaging & web chat β write isolation. Channels and widgets are connection rows rather than scoped data:
NULLmeans shared intake (one WhatsApp number serving every company, sorted by sender), not "Default's". The list stays install-wide, matching how mailbox connections have always behaved, but writes are now gated β an analyst can only pin a channel to a company they can reach, and only edit or delete one that's already theirs. Shared-intake channels remain administerable by anyone managing messaging, since they belong to no single company. Invisible at N=1. - β¬ CMDB, Calendar, Forms, Network Mapper
Still open in messaging:
tenant_channel_sendersβ the per-sender routing map that makes a shared-intake channel work β has no management UI, so rows must be inserted by hand and shared-intake routing isn't practically usable yet. Web chat also reads its channel's company directly instead of going throughresolveTicketTenantForChannel(), so it skips the single-company default and the sender fallback; the two inbound paths should be reconciled.
- β¬ Global requester identity β one login, tickets across companies, each badged
- β¬ Portal scoping + the "only your own tickets" guardrails
- β Per-company SSO β requesters sign in via their own company's identity provider, routed by email domain (0 / 1 / 2+ IdPs β local / straight-through / picker); single-company and MSP both supported, invisible at N=1. Uses each provider's own credentials today; the credential-free "MSP multi-tenant app + consent" model is designed but not built. See Single Sign-On.
- β README β an "in development" multi-tenancy section is now in the project README
- β Admin visibility tools β System β Topology (a company-rooted tree of mailboxes, domains, providers, analysts, ticket/requester counts across every company) and System β Orphaned tickets (find + rehome tickets stuck in a deleted department, which are hidden from every team queue). See System.
- β¬ Isolation beyond the tickets module β other modules that read tickets (self-service portal, CMDBβtickets, Tasksβtickets, Watchtower, the SLA cron, the workflow engine, templated/CSAT email) are not yet company-scoped; only matters at N>1, fix as each module gets its Phase-3 scoping. See Data isolation.
- β¬ Companies: delete / archive management (today you can only deactivate)
- β¬ Automated isolation tests (guarding against one company seeing another's data)
- β¬ Tidy-up: remove the
api/tickets/debug_get_attachment.phpdiagnostic (discloses server paths)
This page is maintained by hand as work lands β see the GitHub roadmap issue (#24) for live discussion.
FreeITSM β an open-source IT Service Management platform Β· github.com/edmozley/freeitsm Β· MIT licence
- Installation
- β° Scheduled tasks (cron jobs)
- Architecture
- AI Providers
- Internationalisation (i18n)
- Timezones & Time Handling
- Theming & Dark Mode
- β¨οΈ Command palette (βK)
- π Searching inside tickets
- π Attached documents
- MobileβFriendly
-
Security
- Layer 1 β which modules you can enter
- β³ π§© Module Access Control
- β³ π οΈ Module Access β Developer Guide
- Layer 2 β what you can administer
- β³ π Roles & Permissions
- β³ π οΈ Roles β Developer Guide
- β³ π€ Why capabilities are constants
- Layer 3 β the System module
- β³ π Admin Access Control
- Hardening
- β³ π Security review response 2026-08
- β³ π‘οΈ Security hardening 2026-08
- β³ π οΈ Security hardening 2026-08 β Developer Guide
- β³ π‘οΈ Round three β plain English
- β³ π οΈ Round three β Developer Guide
- Single Sign-On (SSO)
- ποΈ LDAP & Active Directory
- Browser Extension
- API Reference
-
π REST API β how it works
- β³ π« REST API: Tickets
- β³ π» REST API: Assets
- β³ π΄ REST API: Problems
- β³ π REST API: Changes
- β³ π REST API: Knowledge
- β³ β REST API: Tasks
- β³ ποΈ REST API: CMDB
- β³ π REST API: Contracts
- β³ ποΈ REST API: Calendar
- β³ πΏ REST API: Software
- β³ π¦ REST API: Service Status
- β³ βοΈ REST API: Morning Checks
- β³ π REST API: Forms
- β³ βοΈ REST API: Workflow
- β³ πΊοΈ REST API: Network Mapper
- β³ π§ Using the API docs page
- β³ π OpenAPI specification
- β³ β OpenAPI: kept correct
- β³ π οΈ Maintaining the catalogue
- Watchtower
-
Tickets
- β³ Mailbox Authentication
- β³ π€ Email send log
- β³ Basic IMAP mailboxes
- β³ Email rendering & images
- β³ SLA Management
- β³ WhatsApp channel
- β³ π¬ Web chat channel
- β³ π£ Slack channel
- β³ π Linking tickets
- β³ ποΈ Canned responses
- β³ βοΈ Limiting replies to particular senders
- β³ βοΈ Email signatures
- β³ π The public web address
- β³ π Raising a ticket for someone else
- β³ π Merging tickets
- β³ β Splitting tickets
- β³ β Selecting several tickets
- β³ π οΈ Snoozing tickets β Developer Guide
- β³ π₯ Collision detection
- β³ β±οΈ Time tracking
- Problem Management
- Tasks
- Assets
- Knowledge
- Change Management
- Calendar
- Morning Checks
- Reporting
- Software
- Forms
- Contracts
- Service Status
- π Notifications
- π¨ War Room
- Self-Service Portal
- LMS
- Process Mapper
- CMDB
- Network Mapper
- Workflows
- Issue trackers (Jira, Azure DevOps)
- System
-
Overview
- β³ π Progress tracker
- β³ Concepts & vocabulary
- β³ Email routing & mailboxes
- β³ Settings: global vs per-company
- β³ Users & self-service
- β³ Staff cross-company access
- β³ Worked examples
- β³ Pitfalls & gotchas
- β³ Scope: what it's for
- β³ π οΈ Developer Guide (make a module multi-company)
- β³ ποΈ Case study: CMDB (a linked graph)
- β³ π§ͺ Test harness (prove it's isolated)