Send-RjReportEmail
Send branded HTML report emails from Azure Automation runbooks via Microsoft Graph using Markdown content.
Overview
Send-RjReportEmail is the standard helper for delivering report emails from RealmJoin reporting runbooks. It takes Markdown content, converts it to a RealmJoin-branded responsive HTML email, attaches optional files and inline branding graphics (header/footer), and sends the result through the Microsoft Graph sendMail endpoint.
Key characteristics:
Markdown in, HTML out — runbooks compose the report body in Markdown; the function renders it to themed HTML that works across Outlook Classic, New Outlook, Outlook Web, mobile clients and dark mode.
One email per recipient — when multiple recipients are supplied, the function sends an individual message to each address rather than a single multi-recipient mail. This is a privacy/BCC-by-default design.
Inline branded header & footer — bundled PNG assets are sent as CID attachments and referenced by the embedded HTML. Both can be overridden or suppressed entirely.
Self-connecting — if no Graph session is active, the function transparently calls
Connect-RjRbGraph(orConnect-MgGraph -Identitywhen-UseNativeGraphRequestis set).Resilient — failed attachment reads, missing image overrides, or per-recipient sendMail failures are reported but do not abort the entire batch unless all recipients fail.
Centralized email settings (sender address, service desk info) are documented in Runbook Report Settings — this document focuses on calling the function from a runbook.
Prerequisites
Sender mailbox
A licensed Microsoft 365 mailbox (typically a dedicated shared mailbox such as [email protected]) is required as the From address. The Automation Account's managed identity must be allowed to send on behalf of that mailbox via the Graph Mail.Send application permission (scoped via RBAC for Applications if you want to restrict the identity to a single mailbox).
Graph permissions
Default (Invoke-RjRbRestMethodGraph)
Mail.Send (Application) on the sender mailbox
With -UseNativeGraphRequest
Same — the call still hits /users/{id}/sendMail
Module connectivity
By default the function uses Invoke-RjRbRestMethodGraph from this module. If no connection is active it auto-connects via Connect-RjRbGraph. When -UseNativeGraphRequest is set, the function instead checks Get-MgContext and calls Connect-MgGraph -Identity -NoWelcome on demand.
Quick Start
The minimum viable call requires only the sender, the recipient, a subject and the Markdown body:
This produces a fully branded RealmJoin email with the default header and footer, light/dark mode support, and the tenant/version block in the footer.
Parameters
Required
EmailFrom
string
User principal name or object id of the sender mailbox. Used as /users/{id}/sendMail.
EmailTo
string
Recipient address. Single string — multiple addresses are passed as a comma-separated list, see below.
Subject
string
Subject line. Also injected into the HTML <title> element.
Optional — Content
Attachments
string[]
@()
Local file paths to attach. Missing files are logged and skipped, unreadable files emit a warning but do not abort the send. MIME type is derived from the file extension.
saveToSentItems
bool
$true
If $true the sent message is kept in the sender mailbox's Sent Items. Set to $false for high-volume reporting to avoid filling the mailbox.
TenantDisplayName
string
—
Shown in the tenant-info box embedded at the end of the content area.
ReportVersion
string
—
Shown in the tenant-info box (use semantic version strings, build numbers, or a runbook name + date).
Optional — Branding
HeaderImage
string
bundled Assets/Header.png
Local file path to a PNG/JPG/GIF that overrides the default header graphic. The runbook must resolve any URL/blob to a local file beforehand (e.g. via Get-AzStorageBlobContent). Missing/unreadable overrides fall back to the bundled default and emit a warning.
FooterImage
string
bundled Assets/Footer.png
Same handling as HeaderImage. The footer is rendered as a single clickable image — any branding text, logo or URL must be baked into the PNG.
FooterLink
string
https://www.realmjoin.com
URL used as the href and title of the anchor wrapping the footer image.
NoHeader
switch
off
Suppresses the header graphic entirely. If combined with HeaderImage, a warning is emitted and the override is ignored.
NoFooter
switch
off
Suppresses the footer graphic and its link entirely. If combined with FooterImage or a custom FooterLink, a warning is emitted and those values are ignored.
Recommended image dimensions: 750 × 200 px PNG. This matches the email container width and the bundled defaults. Significantly different aspect ratios may look distorted on narrow viewports. Each graphic should stay well below 3 MB — Graph caps the total sendMail request at 4 MB and a warning is emitted if either image exceeds 3 MB.
Optional — Transport
UseNativeGraphRequest
switch
off
Sends via Invoke-MgGraphRequest (requires Microsoft.Graph module and a Connect-MgGraph session) instead of Invoke-RjRbRestMethodGraph. Use this when the runbook is built around the native SDK rather than the RealmJoin wrapper.
Usage Examples
Multiple recipients
EmailTo accepts a single string containing one or more comma-separated addresses. Each address is trimmed, empty entries are removed, and one individual email is sent per recipient — recipients do not see each other.
With attachments and tenant metadata
The attached files are listed in an "Attached Files" box at the bottom of the email body, in addition to being added as real attachments to the message.
Custom header/footer branding
Bring your own branding by downloading the assets to a local path first, then pass the resulting paths. The function does not fetch URLs itself.
If $headerPath is missing or unreadable, the call still succeeds — the bundled RealmJoin default is used and a warning is logged.
Plain content (no header/footer)
For alert-style notifications that should not look like a marketing email:
Using the native Microsoft.Graph SDK
If the runbook is already authenticated through Connect-MgGraph (managed identity) and you prefer not to mix in the RealmJoin wrapper:
Reading the report body from a file
For larger reports, generate the Markdown to a .md file and read it in:
Markdown Support
The function ships with a built-in lightweight Markdown → HTML converter. No external Markdown module is required. Supported syntax:
# … ###### headings
All six levels. Space after # is optional. h1 gets an underline; spacing tuned for Outlook.
**bold**, *italic*, ~~strike~~
Inline-only (must not span multiple lines).
`inline code`
Rendered as <code> with a light grey background.
lang ... fenced code blocks
Language tag is preserved as class="language-…". Also tolerates malformed single-backtick fences.
[text](url) links
Open in new tab with noopener noreferrer.
 images
Inserted as <img> (no inline-attachment magic — the URL must be reachable by the mail client).
- item / 1. item lists
Nested lists supported via 2-space indentation per level. Mixing ordered and unordered closes the previous list.
Multi-line list items
An indented, non-empty line directly under a <li> is folded into the same item with a <br> soft break — no need to keep each item on one line.
- [ ] / - [x] task lists
Rendered as ☐ / ☑ Unicode glyphs (green when checked). <input type="checkbox"> is intentionally avoided because Outlook Classic strips form controls. Capital [X] also counts as checked.
> blockquote
Rendered with a coloured left border and shaded background.
> [!NOTE|TIP|IMPORTANT|WARNING|CAUTION]
GitHub-style admonitions. First line of the blockquote is the marker (alone), the remaining >-prefixed lines are the body. Each type gets its own accent colour, glyph and title bar.
---, ***, ___
Horizontal rule.
|col|col| tables
Standard pipe tables with :---, :---:, ---: alignment specifiers. Header row + separator required.
\\ escaping
\*, | etc. are honoured so literal Markdown characters can be emitted.
Items not supported include footnotes, definition lists, and HTML pass-through — keep the Markdown to the table above.
Behavior & Error Handling
Recipient parsing
EmailTo is split on commas, each entry is trimmed and empty entries are dropped. If the resulting list is empty the function throws No valid email recipients found in EmailTo parameter. before any Graph call is made.
Per-recipient failures
Each recipient is sent independently. The function tracks successes and failures:
If at least one send succeeds but others fail, a warning is emitted listing the failed addresses; the function returns normally.
If all sends fail, the function throws
Failed to send email to all recipients: …so the runbook fails loudly.
Attachment failures
Missing files (path does not exist) — logged verbosely, silently skipped.
Existing but unreadable files (locked, permission denied) — warning emitted, skipped, the rest of the call proceeds.
The "Attached Files" box at the bottom of the email lists only attachments that were successfully read.
Image override failures
Both HeaderImage and FooterImage fall back to the bundled defaults on any error (missing file, unsupported extension, IO error). A warning describes the failure and identifies which default was used.
Total size limit
Graph caps sendMail requests at ~4 MB combined (HTML body + all attachments, base64-encoded). The function emits a warning when either branding image exceeds 3 MB. If the total payload still exceeds 4 MB the Graph call itself will fail; consider:
Uploading large data to the Storage Account channel instead — see Runbook Report Settings.
Linking to externally hosted attachments rather than embedding them.
Compressing tabular data (
Compress-Archive) before attaching.
Integration with Runbook Report Settings
Reporting runbooks typically resolve the sender address from the central RealmJoin customization JSON rather than hard-coding it. The relevant settings are documented in Runbook Report Settings. A typical resolution pattern in a runbook looks like:
Outputs
The function returns nothing on success. All progress is written via Write-RjRbLog -Verbose (visible when the runbook is run with -Verbose or $VerbosePreference = 'Continue'). Warnings are forced through $WarningPreference = 'Continue' regardless of caller-side overrides, so they reliably appear in the Azure Automation job stream.
See Also
Runbook Report Settings — central configuration of the sender mailbox, service desk info, and Storage Account delivery channel.
Microsoft Graph: Send mail — underlying API.
Last updated
Was this helpful?