Appearance
Microsoft 365 (via CIPP)
OpsMerge connects to your clients' Microsoft 365 tenants through CIPP, the open-source M365 management tool that you self-host. We don't have a direct multi-tenant M365 app — we use yours.
For the day-one walkthrough of getting this set up, see Connect Microsoft 365 in Getting started. This article is the reference.
Why through CIPP, not direct
Two reasons:
- CIPP already does the hard part. Multi-tenant M365 OAuth via the Secure App Model, tenant onboarding wizards, and the dozens of edge cases that come with M365 partner relationships — CIPP has solved all this. Replicating it badly was unappealing.
- Most MSPs we talked to during the OpsMerge build were already running CIPP. Asking them to consent to a second multi-tenant app would have been a hard sell.
So we treat CIPP as a first-class dependency — your CIPP instance is the bridge between OpsMerge and M365.
What you need
- A CIPP deployment you control (typically an Azure Static Web App; CIPP docs cover the install).
- A CIPP-API client with read access to all the tenants you want OpsMerge to see. Create it in CIPP under Settings → CIPP API → Actions → Add CIPP-API Client (role: readonly).
- An admin user in OpsMerge (you'll configure the CIPP connection from Settings).
The connection (once per OpsMerge tenant)
Settings → Integrations → CIPP Sync.
Fill in all four fields:
- CIPP URL — your CIPP instance's API URL, e.g.
https://yourname.azurewebsites.net. Found under Function Authentication at the top of the CIPP API page. Must start withhttps://. - Tenant ID — the Azure AD tenant ID of your CIPP instance, also shown under Function Authentication.
- Client ID — the Client ID of the CIPP-API client you created (a UUID), copied from the CIPP-API Clients table.
- Client Secret — generate it via the three dots on your API client's row → Reset Secret. Copy it immediately; CIPP only shows it once.
Click Connect. OpsMerge requests an Azure AD token using these credentials and stores them (the secret is encrypted at rest).
One CIPP, one OpsMerge tenant
If you're an MSP-of-MSPs (sub-tenanting OpsMerge to other MSPs), each sub-MSP needs their own CIPP instance — CIPP isn't designed to be partitioned by a third party.
Per-client tenant linking
For each client whose M365 tenant you want OpsMerge to track:
- Client → M365 tab → Link CIPP tenant.
- Pick the matching tenant from the dropdown (pulled from CIPP's
listTenants). - Save.
OpsMerge starts an initial sync immediately. The first sync of a tenant with under 200 users completes in 30–90 seconds.
What gets synced
For each linked tenant, we read:
- Users — every M365 user account. Mapped to contacts in OpsMerge by primary email (case-insensitive, deduped).
- Mailboxes — type (regular / shared / resource), licence, size.
- Devices — Intune-managed devices. Added to the client's asset list, annotated as M365-managed.
- Domains — primary + accepted domains. Auto-populates client domain fields if empty.
- Licences — current subscription state across the tenant.
- Security baseline — Defender, conditional access policies, MFA enrolment summary.
What we don't sync (yet):
- Mailbox content (we never read mail bodies, just metadata).
- Teams chat content.
- SharePoint file contents (we see structure metadata only).
- Audit logs (we have our own audit log; M365's is separate).
Sync cadence
- Initial sync on first linking: 30–90s for a typical tenant, longer for very large ones.
- Ongoing sync is poll-based with adaptive cadence:
- Hourly when there's no recent activity at the client.
- Every 10 minutes when the client has open tickets, recently-installed agents, or other signs of active engagement.
- On-demand: Client → M365 tab → Sync now.
We don't use webhooks. M365's change-notification surface (via CIPP) isn't reliable enough for what we need.
Write actions (opt-in)
By default the integration is read-only. To enable specific write actions:
Settings → Integrations → CIPP Sync → Allow CIPP writes → On.
Available write actions (when enabled):
- Suspend / unsuspend a user.
- Force password reset.
- Add to / remove from groups.
- Block / unblock sign-in.
- Reset MFA.
These actions are gated by an additional permission (m365.write) — only roles with that permission can fire them. Audit log captures every write with who, when, against which tenant.
Write actions flow OpsMerge → CIPP → M365. If CIPP's connection to a tenant is broken, the write fails and surfaces in the audit log.
Auto-creating contacts from M365 users
By default, an M365 user is matched to an existing OpsMerge contact if the primary email matches — otherwise the user is shown in M365 view but not added as a contact.
To auto-create contacts from M365 users:
- Client → M365 tab → Settings → Auto-create contacts → On.
This is useful for "every M365 user should be visible as a contact in the client portal" patterns. Be aware: shared mailboxes and resource accounts also become contacts unless you exclude them.
Auto-monitor M365 domains
Client → M365 tab → Domains → Auto-monitor.
Adds every accepted domain of the tenant to Domain monitoring. Cheaper than manually adding domains for SSL/DMARC/MX monitoring.
Common patterns
"Pre-populate contacts from M365, then take CIPP out of the picture"
You can. Sync once, take a snapshot of contacts, then unlink. The contacts stay (they were imported into OpsMerge's own database). The link breaks; subsequent M365 changes won't reflect.
Not generally recommended — keeping the link active is more useful — but valid.
"I want CIPP only for a subset of clients"
Don't link tenants you don't want synced. OpsMerge only pulls data for linked tenants; unlinked tenants in CIPP are visible-but-ignored.
"M365 user count for billing"
The Per M365 user @ £X recurring line in a contract reads the live count of M365 users at this client. See Recurring invoices for the proration model.
Common issues
Tenant list shows the tenant but linking fails with "tenant not found". Stale CIPP cache. In CIPP, hit Settings → Refresh tenant list, then retry.
"No tenants visible" after a successful API connection. Your CIPP-API client has the wrong scope. In CIPP, give it access to all tenants and reconnect OpsMerge.
User import created duplicates of an existing contact. Primary-email mismatch. The CIPP-imported user's primary email differs from your manually-entered contact's email even slightly. Merge from the contact list; OpsMerge remembers the mapping for next time.
Sync seems slow / lagging. Adaptive polling backs off when there's no activity. For an active client, force a sync from the M365 tab; for a quiet one, hourly polling is enough.
CIPP base URL behind Cloudflare Access or an IP allowlist. OpsMerge's outbound IP needs to be allow-listed. The current IP for the EU cell is in Settings → Integrations → Network ranges.
Write action fails with "Insufficient permissions". Your CIPP-API client has read access but not write. Raise its role in CIPP and reconnect OpsMerge.
Disconnecting
To unlink a client from CIPP:
- Client → M365 tab → Disconnect.
- Confirm.
OpsMerge stops syncing. Imported data stays in place but is marked as "no longer syncing". You can re-link to the same or a different tenant at any time.
To completely remove the CIPP connection at the OpsMerge-tenant level: Settings → Integrations → CIPP Sync → Disconnect. All client-tenant links break.
Next
- Connect Microsoft 365 — first-time setup walkthrough
- Clients & contacts — how M365-imported contacts behave
- Email gateway — sometimes coordinated with M365 mail flow