Repository navigation
Onboarding Your First Organization
TL;DR: platform admin creates the org and (ideally) sets its Entra tenant
ID → the org's first user signs in and becomes its org_admin → the org
connects a mailbox and completes two Microsoft-side consent steps → domains
get verified → checks start running.
This is the actual current flow, kept in sync with the app rather than a design-time plan — steps here are what the UI does, from the app's multi-tenant model: an "operator" who can create/manage organizations, and the organizations themselves.
In the admin console (/admin), enter the organization's name and its
Entra tenant ID (the GUID for its Microsoft 365 / Azure AD tenant —
found in its Entra admin center under "Tenant properties"). Providing the
tenant ID up front activates the organization immediately.
If you don't have the tenant ID yet, you can still create the organization
with just a name (status stays pending_setup) and add the tenant ID later
from the same screen — nothing else is blocked on it except that
organization's users being able to sign in.
That's the only manual step on the platform-admin side. Everything from here is self-service from the organization's own dashboard.
The organization's users go to the dashboard and sign in with "Sign in with Microsoft." Microsoft handles SSO consent automatically inline at first sign-in — there's no separate consent link to hand out for this part, unless the organization's tenant is configured to block user consent entirely, in which case an admin-consent link for the SSO app is available from the mailbox setup screen (see below) and its Global Admin needs to click it once.
The first user to sign in for a newly-activated organization automatically
becomes that organization's org_admin, and anyone else in the tenant can
sign in too. Before handing out the dashboard address, set Assignment
required on the sign-in app in the organization's tenant and assign the
intended admin first; see
Securing Your Install.
On first visit to the dashboard, a banner prompts for the shared mailbox
address that receives DMARC/TLS-RPT reports (e.g.
dmarc-reports@yourdomain.com). Entering it and clicking "Save & start
syncing" is enough to kick off the first sync attempt — consent is
self-attested at this point, so the banner will initially show green even
before the underlying Graph permissions are actually in place.
This step alone is not sufficient for reports to actually flow in. Two more things need to happen on the organization's side, both surfaced as links right there in the banner (click "Entra links" once the banner is up, or — if the first sync fails — the same links appear automatically under the sync-error message):
-
Mail access consent (required): the organization's Global Admin clicks the mail-access admin-consent link and approves it. This grants the app's Mail Access application (app-only,
Mail.Read) permission in its tenant. -
Limit the app to the reports mailbox (required, done outside this app, in Exchange Online PowerShell). This is the step that actually matters for security: the consent in step 1 grants app-only
Mail.Read, which on its own can read every mailbox in the tenant, not just the reports mailbox. Pick one of the two ways below.Option A: RBAC for Applications (Microsoft's current way; it replaces Application Access Policies):
Connect-ExchangeOnline # The IDs from Entra → Enterprise applications → the mail access app # (not App registrations, which shows different IDs). New-ServicePrincipal -AppId <mail-access-app-id> -ObjectId <enterprise-app-object-id> -DisplayName "YetAnotherDmarcTool mail access" New-ManagementScope -Name "YetAnotherDmarcTool reports mailbox" ` -RecipientRestrictionFilter "PrimarySmtpAddress -eq 'dmarc-reports@yourdomain.com'" New-ManagementRoleAssignment -App <enterprise-app-object-id> -Role "Application Mail.Read" ` -CustomResourceScope "YetAnotherDmarcTool reports mailbox"
Then remove the tenant-wide
Mail.Readgrant from step 1. This is not optional: Exchange adds the two grants together, so as long as the Entra grant exists the scope above limits nothing (Microsoft's FAQ on this).Connect-MgGraph -Scopes "Application.ReadWrite.All","AppRoleAssignment.ReadWrite.All" $sp = Get-MgServicePrincipal -Filter "appId eq '<mail-access-app-id>'" # Mail.Read (application) on Microsoft Graph has app role ID 810c84a8-4a9e-49e6-bf7d-12d183f40d01 Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $sp.Id | Where-Object AppRoleId -eq "810c84a8-4a9e-49e6-bf7d-12d183f40d01" | ForEach-Object { Remove-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $sp.Id -AppRoleAssignmentId $_.Id }
Check it:
Test-ServicePrincipalAuthorization -Identity <mail-access-app-id> -Resource dmarc-reports@yourdomain.comshould showInScope: True, and the same command against any other mailboxInScope: False. That test doesn't see Entra grants, so also check that Entra → Enterprise applications → the app → Permissions no longer listsMail.Read. Changes can take 30 minutes to 2 hours to apply. Don't approve the consent link from step 1 again afterwards: that brings the tenant-wide grant back.Option B: Application Access Policy (older, still works; it limits the Entra grant itself, so nothing needs removing):
New-DistributionGroup -Name "DMARC Dashboard Mailbox Access" -Members dmarc-reports@yourdomain.com New-ApplicationAccessPolicy -AppId <mail-access-app-id> ` -PolicyScopeGroupId "DMARC Dashboard Mailbox Access" -AccessRight RestrictAccess ` -Description "Restrict DMARC Dashboard's Graph token to the DMARC reports mailbox only" Test-ApplicationAccessPolicy -AppId <mail-access-app-id> -Identity dmarc-reports@yourdomain.com
Test-ApplicationAccessPolicyshould report access granted for the reports mailbox and denied for any other.The same applies to the operator's hosted reporting mailbox: limit the mail access app to that one mailbox in the operator's own tenant.
If the organization sets the mailbox address before completing these two steps (a common ordering, since it's the more discoverable action), the first sync will fail. That's expected — the dashboard shows the same consent-guidance links directly next to the sync error, with no dead end. More on diagnosing a stuck sync: Report Ingestion Troubleshooting.
Once both consent steps are done, click "Resync now" (or wait for the next
scheduled poll, every 10 minutes). The mailbox banner should turn green with
a recent "last synced" timestamp. As platform admin, /admin/job-runs shows
every mailbox-poll attempt across all organizations — a success row for
this org's mailbox_poll job confirms it end-to-end, and doubles as
confirmation that the Application Access Policy is scoped correctly (a
mis-scoped policy fails here, not silently).
Add each domain/subdomain to monitor. Before any best-practice checks run against it, the domain must prove DNS control:
- Click "How to verify" on the newly-added domain to get a TXT record name
(
_yadt-verify.<domain>) and a token value. - Publish that TXT record in the domain's real DNS.
- Click "Check now" — once the record resolves, the domain flips to
verifiedand checks start running.
A subdomain of an already-verified apex domain inherits verification automatically and doesn't need its own token. See DNS Verification Issues if a check keeps failing.
DKIM selectors aren't discoverable via DNS alone, so they have to be added
manually (whatever selector the mail provider uses — check its provider's
DKIM setup docs, e.g. selector1/selector2 for Microsoft 365, google
for Google Workspace). Without at least one selector added, the DKIM check
just reports "no selectors registered" rather than a real result.
Once verified (and with selectors added), SPF/DKIM/DMARC/DMARCbis/MX/ MTA-STS/DANE/TLS-RPT checks run automatically on a schedule (see How DNS Checks Are Scheduled), or on-demand via "Recheck." This app is read-only and advisory toward the organization's DNS — it never writes records on their behalf. Every finding is a recommendation; the organization updates its own authoritative DNS (at its registrar or DNS host) to act on it.
None of the above requires the platform admin to log in more than once (to create the org). For reference, platform-admin auth itself works two ways:
- Local email + password (the break-glass account, bootstrapped at deploy time) — has a "Change password" option in the admin console.
-
Via your own operator organization's Entra login, if that organization
is flagged as the operator org — sign in normally as an
org_adminof that org and you land in the same admin console.
← Back to the README · Live demo · Report an issue · MIT licensed
- Quick Start: Docker Compose
- Deploying with Portainer
- Deploying on Azure
- Configuration Reference
- Onboarding an Organization
- IMAP Mailbox
- Google Workspace Mailbox
- Sign-in Providers
- Multi-Tenancy & RLS
- DMARC Subdomain Inheritance
- Report Attribution
- SPF Conditional Mode
- Detected Domains
- DNS Check Scheduling
- Domain Rating
🏗️ Architecture
🗺️ Roadmap