Skip to main content

Getting Started

dotMARC needs two separate Entra app registrations: do not reuse one for both purposes.

1. Mailbox access (app-only)​

  1. App registrations → New registration, name it e.g. dotmarc-mailbox.

  2. API permissions → add Microsoft Graph Application permission Mail.Read, then grant admin consent. To send client reports through Graph, also add Mail.Send.

  3. Certificates & secrets → create a client secret.

  4. Restrict this app's mailbox access via an Exchange Application Access Policy. Exchange requires the policy scope to be a security principal (for example a mail-enabled security group), not the mailbox itself. Create a dedicated group for the DMARC reports mailbox, add that mailbox to the group, and then scope the policy to the group:

    Connect-ExchangeOnline -Organization <your-tenant>

    $group = New-DistributionGroup -Name "dotMARC DMARC Reports Scope" -Type Security -Alias dotmarc-dmarc-reports
    Add-DistributionGroupMember -Identity $group.Identity -Member "[email protected]"

    New-ApplicationAccessPolicy -AppId <client-id> -PolicyScopeGroupId $group.ObjectId -AccessRight RestrictAccess -Description "dotMARC: restrict to DMARC reports mailbox only"

    If you already have the mail-enabled security group, use its ObjectId instead of the mailbox address. Do not pass the mailbox address directly to -PolicyScopeGroupId.

0 of 4 complete

2. Dashboard sign-in (delegated)​

  1. App registrations → New registration, name it e.g. dotmarc-dashboard.
  2. Authentication → add a Web platform redirect URI: https://<your-deployment-host>/signin-oidc. On the same page, under Implicit grant and hybrid flows, check ID tokens - Microsoft.Identity.Web's OIDC middleware requests an id_token as part of its hybrid authorization-code flow, and without this box checked sign-in fails at the Entra consent step (the redirect back to /signin-oidc never happens).
  3. Certificates & secrets → create a client secret. Microsoft.Identity.Web wires this app up via the standard confidential-client authorization-code flow, so the token exchange after sign-in needs this secret even though the app itself is only used for interactive sign-in.
  4. No API permissions needed beyond the default User.Read.
0 of 4 complete

Configure​

Set via environment variables (double-underscore nesting):

VariableDescription
Graph__ClientIdMailbox app registration's client ID
Graph__TenantIdYour tenant ID
Graph__ClientSecretMailbox app registration's client secret
Graph__MailboxAddressThe shared mailbox address receiving DMARC reports
Graph__PollIntervalSecondsDefault 300
EntraId__TenantIdYour tenant ID
EntraId__ClientIdDashboard app registration's client ID
EntraId__ClientSecretDashboard app registration's client secret
ConnectionStrings__DotMarcPostgreSQL connection string; defaults to Host=localhost;Database=dotmarc;Username=dotmarc;Password=dotmarc
InitialAdmins__EmailsComma-separated list of email addresses granted the Admin role the very first time the app starts with no existing access grants either a genuinely fresh install, or this app's first deploy of the permissions feature to an existing live environment. Only takes effect while the UserAccess table is empty; harmless to leave set afterwards.
Set InitialAdmins__Emails before deploying this feature

Authorization is deny-by-default: with no existing access grants, the fallback policy locks out every user, including the operator, unless InitialAdmins__Emails seeds at least one Admin grant on that first startup. If you deploy without it, recovery requires direct database access to insert a UserAccess row pointing at a locked Admin role.

Choose how to run it​

Both paths run the exact same product image and cover the same ground end to end - pick whichever fits how you already operate:

Deploy with DockerDeploy to Azure
Best forSelf-hosters: your own hardware, a home/office server, or any VPSTeams already running infrastructure on Azure
You manageThe host, Docker, TLS (a bundled Caddy setup handles this for you)Nothing beyond the app config - Container Apps handles the rest
DatabasePostgreSQL container, your own volume/backupsManaged Azure Database for PostgreSQL Flexible Server
Cost modelWhatever your own hardware/VPS costsPay-as-you-go Azure resources

Not sure? Deploy with Docker is the faster path to a first working instance - a single docker compose command gets you a complete stack, including HTTPS. Deploy to Azure trades a bit more setup for infrastructure you don't have to maintain yourself.

What you'll see​

Once your first DMARC report arrives (this looks the same regardless of which path you chose above), the dashboard shows pass/fail rates, DNS status, and polling status across every monitored domain:

dotMARC dashboard showing pass/fail rates, DNS status, and polling status across monitored domainsdotMARC dashboard showing pass/fail rates, DNS status, and polling status across monitored domains

Drill into any domain for a 30-day pass/quarantine/reject chart, its DMARC record status, and a breakdown of why any rejected/quarantined mail happened - a benign forwarder tripping alignment reads very differently from no reason given at all:

dotMARC domain detail page showing a 30-day pass/fail chart and reject/quarantine reason breakdown for cobalt-freight.exampledotMARC domain detail page showing a 30-day pass/fail chart and reject/quarantine reason breakdown for cobalt-freight.example

The Sources tab breaks that down further per source IP, with the actual SPF/DKIM domain and result behind each pass/fail on hover, plus who owns the IP:

dotMARC Sources tab showing per-source volume, SPF/DKIM results, reject reason, and IP ownershipdotMARC Sources tab showing per-source volume, SPF/DKIM results, reject reason, and IP ownership

Next: Deploy with Docker or Deploy to Azure to run it in production, or Local Development to run and test from source. Once you're up and running, DNS Provider Push lets dotMARC fix DNS records for you instead of copying them in by hand.