Email Notifications
Configure email notifications for PIM Monitor scan results.
Quick Setup
-
Grant permissions to service principal:
- Azure AD → App registrations → [Your app]
- API permissions → Add permission
- Select Microsoft Graph → Application permissions
- Search for Mail.Send → select it → Grant admin consent
-
Set environment variables (in your pipeline):
NOTIFICATION_EMAIL= recipient email addressNOTIFICATION_MAIL_FROM= service principal UPN (sender mailbox)
-
Test: Run the pipeline; you should receive an email
Email Components
Header
The email header identifies it as a PIM Monitor notification:
- Brand: "pim/monitor" (monospace font, red color)
- Subtitle: "Change Notification" (for regular changes) or "Scan Errors" (for errors)
- Timestamp: ISO 8601 UTC
Severity Summary
High-level count of detected changes by severity:
- Red: High-severity changes (e.g., new role, permanent assignment)
- Orange: Medium-severity changes (e.g., policy updates, expiration)
- Yellow: Low-severity changes (e.g., removals, minor updates)
- Gray: Informational changes (e.g., display name changed)
Example:
[PIM Monitor] 2 High, 3 Medium, 1 Low changes
Changes by severity:
High (2) ████████████████████░░░░░░░░
Medium (3) ██████████████░░░░░░░░░░░░░░░
Low (1) █████░░░░░░░░░░░░░░░░░░░░░░░
Change Details
For each change, the email includes:
- Description: What changed (e.g., "Directory Roles > Global Administrator > policy")
- Severity: Color-coded badge
- Old vs. new: Collapsible diff with before/after values
- Deep link: Direct link to the entity in Entra portal (for roles/groups)
Collapsible example:
▶ Directory Roles > Global Administrator > policy
Severity: High
OLD → NEW:
- Enablement_EndUser_Assignment.enablement required: true
+ Enablement_EndUser_Assignment.enablement required: false
Diff Links
If your scan commit is pushed to GitHub or Azure DevOps, the email includes links to view the full diff:
- View diff in repository: [link to commit in GitHub/Azure DevOps]
- Timestamp: Time scan completed
Footer
Scan metadata:
- Scanned entities: Count of roles, groups, units checked
- Scan time: When the scan ran (ISO 8601 UTC)
- Commit SHA: Git commit hash (if pushed)
Customizing Email Format
All email formatting is done in src/notifications.ps1.
Edit the HTML Layout
The main HTML formatter is Format-ChangeSummaryHtml (lines 131–278):
function Format-ChangeSummaryHtml {
param(
[Parameter(Mandatory)] $ChangesBySeverity,
[Parameter(Mandatory)] [string] $CommitSha
)
# Build HTML string...
# Access: $ChangesBySeverity.High, .Medium, .Low, .Informational
# Each change has: .workload, .entity, .fileType, .description, .severity
return $htmlString
}
Customize Colors
Edit the inline styles to change colors:
<!-- High severity -->
<div style="color: #dc2626;">...</div> <!-- red, currently #dc2626 -->
<!-- Medium severity -->
<div style="color: #ea580c;">...</div> <!-- orange, currently #ea580c -->
<!-- Low severity -->
<div style="color: #eab308;">...</div> <!-- yellow, currently #eab308 -->
Customize Severity Labels
Edit the header text:
$s = "Bold, Italic, Red, UPPERCASE — whatever you want"
# Example: change from "pim/monitor" to "Azure Identity Governance"
<div style="...">Azure Identity Governance</div>
Remove or Reorder Sections
Delete or rearrange parts of the HTML:
# Example: remove commit diff links
# Delete or comment out the section that builds $commitDiffUrl
# Example: add a custom footer with approval instructions
<div>Please review and approve all High changes within 24 hours</div>
Change the Subject Line
Edit the subject construction in Send-EmailNotification (notifications.ps1, line ~750):
$subject = "[PIM Monitor] $($changesBySeverity.High.Count) High, $($changesBySeverity.Medium.Count) Medium"
# Change to:
$subject = "[SECURITY] $($changesBySeverity.Total) PIM changes detected"
Email Configuration
Service Principal Setup
The service principal sending email needs:
- Mail.Send permission on Microsoft Graph
- Exchange Online mailbox (if using shared mailbox, grant Send As permission)
Grant Mail.Send permission:
- Azure AD → App registrations → [Your app]
- API permissions → Add permission
- Microsoft Graph → Application permissions
- Search Mail.Send → select → Grant admin consent
Verify in Azure AD:
- Go to App registrations → [Your app] → API permissions
- Should see: ✓
Mail.Send(Application)
Sender Mailbox Considerations
The NOTIFICATION_MAIL_FROM must be:
- A real mailbox in your tenant
- Able to send mail (not a resource mailbox)
- Have the service principal as an owner or delegate (if using shared mailbox)
Options:
- Service principal's own mailbox (if assigned Office 365 license)
- Shared mailbox: Grant service principal Send As permission
Grant Send As permission to shared mailbox:
-AccessRights SendAs
Multiple Recipients
To send to multiple addresses, modify the script:
In Scan-PimState.ps1, update the notification block:
$notifEmails = @(
)
foreach ($email in $notifEmails) {
Send-EmailNotification `
-ChangesBySeverity $changesBySeverity `
-ToAddress $email `
-FromAddress $notifFrom `
-AccessToken $token
}
Or use a distribution group as NOTIFICATION_EMAIL:
NOTIFICATION_EMAIL = [email protected]
# (where security-team is a distribution group)
Troubleshooting Email Issues
Email not sending
Check permission errors in workflow logs:
Send-EmailNotification: "Authorization_RequestDenied"
Solutions:
- Verify
Mail.Sendpermission is granted (see Service Principal Setup) - Verify permission has admin consent (not user consent)
- Verify service principal is owner/delegate of shared mailbox (if applicable)
- Wait 5-10 minutes after granting permission for sync
Email sent but not received
Check:
- Is
NOTIFICATION_EMAILcorrect? Check logs for address - Check recipient's spam/junk folder (emails from service principals often flagged)
- Is the mailbox valid? Try sending a test email manually
- Check Exchange Online rules — may be blocking service principal emails
Subject line wrong or missing
Check:
- Are there any detected changes? If 0 changes, email not sent
- Verify
$changesBySeverityis populated before email step - Search logs for "Email not sent" or "skipping email"
HTML rendering looks wrong
Check:
- Email client may not support all CSS; test in Gmail, Outlook web
- Verify all HTML is valid (check for unclosed tags)
- Inline styles only (external stylesheets not supported in email)
HTML Report vs. Email
There are two separate email features:
| Feature | Triggered by | Content |
|---|---|---|
| Change Email | Detected PIM changes | Summary of changes by severity, diffs, deep links |
| HTML Report Artifact | REPORT_ARTIFACT=true | Full scan report (all detected changes, metadata, timestamp) |
Relationship:
- Email is sent automatically when changes are detected
- Report is published as an artifact (if enabled) — stored separately
- Both use same severity classification, but different formatting
Example workflow:
- Scan runs, detects 5 changes
- Email is sent immediately to
NOTIFICATION_EMAIL - HTML report is generated and stored as artifact (if
REPORT_ARTIFACT=true) - User can download report from artifact storage for archival/compliance
Security Considerations
Sensitive Data in Email
Email notifications include:
- Display names of roles, groups, administrative units
- Properties that changed (e.g., "MFA required" → "true")
- Commit links (git diff visible to anyone with repo access)
Recommendations:
- Use private email addresses for
NOTIFICATION_EMAIL - If using shared mailbox, limit access to authorized users
- Emails are not encrypted in transit — use TLS if available
- Review email recipient permissions regularly
Service Principal Credentials
Never commit service principal secrets. Use:
- Azure DevOps: Pipeline → Variables (mark secret)
- GitHub Actions: Settings → Secrets (encrypted)
Related Pages
- Environment Variables — NOTIFICATION_EMAIL, NOTIFICATION_MAIL_FROM
- Notifications — General notification configuration
- Webhook Channels — Teams, Slack, Discord alternatives
- Scan Errors — Error notification format
- Reporting — HTML report artifact setup