Skip to content

Add HTML message format support with stable idempotency for ExchangeOnline OOF#177

Merged
blindzero merged 9 commits intomainfrom
copilot/support-oof-formatted-messages
Feb 10, 2026
Merged

Add HTML message format support with stable idempotency for ExchangeOnline OOF#177
blindzero merged 9 commits intomainfrom
copilot/support-oof-formatted-messages

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 10, 2026

Implementation Plan for HTML OOF Message Support

  • Explore repository structure and understand current implementation
  • Add MessageFormat support to step contract
    • Update Invoke-IdleStepMailboxOutOfOfficeEnsure.ps1 to validate MessageFormat
    • Update step unit tests for MessageFormat validation
  • Implement normalization for idempotency
    • Add private helper Normalize-IdleExchangeOnlineAutoReplyMessage in ExchangeOnline adapter
    • Update EnsureOutOfOffice method to use normalization for comparison
    • Add provider unit tests for normalization scenarios
  • Update documentation
    • Regenerate step reference documentation
    • Update provider documentation with HTML examples
    • Add HTML OOF example to workflow templates
  • Run final tests and verification
    • Run Pester tests (all passing)
    • Run ScriptAnalyzer (no issues)
    • Code review and security scan (completed, feedback addressed)
  • Fix: Remove global FromFile pattern
    • Reverted Resolve-IdleWorkflowTemplates.ps1 to original state
    • Removed FromFile tests and example files
    • Updated documentation to show host-side file loading approach
  • Fix: Remove short-circuit checks in idempotency logic
    • All configuration properties now checked independently
    • ExternalAudience changes no longer masked by earlier checks
  • Fix: Make whitespace normalization more conservative
    • Changed 2+ spaces→1 to 3+ spaces→2 (preserves double-spacing)
    • Changed 3+ newlines→2 to 4+ newlines→3 (preserves intentional spacing)
    • Reduces risk of false negatives where distinct messages incorrectly compare equal

Summary

Successfully implemented support for formatted HTML auto-reply messages in ExchangeOnline Out of Office (OOF) configuration with stable idempotency.

Core features:

  • New MessageFormat parameter ('Text' | 'Html') with data-only validation
  • Conservative HTML normalization for stable idempotency (handles Exchange server-side canonicalization)
  • Independent idempotency checks for all configuration properties
  • Comprehensive documentation and examples
  • Full test coverage (27 provider tests + 15 step tests, all passing)
  • No breaking changes (backward compatible)

Architecture improvements:

  • Removed global FromFile pattern to prevent backward compatibility issues
  • Fixed idempotency check logic to evaluate all fields independently
  • Made normalization more conservative to avoid false negatives
  • Documented host-side file loading as alternative for large HTML templates
Original prompt

This section details on the original issue you should resolve

<issue_title>ExchangeOnline OOF support formatted HTML auto-reply messages with stable idempotency</issue_title>
<issue_description>## Problem Statement

Operators often configure Exchange Online automatic replies using the Outlook / OWA GUI, which supports formatted messages (HTML markup: bold, links, lists, line breaks).

IdLE's IdLE.Step.Mailbox.EnsureOutOfOffice currently accepts Config.InternalMessage and Config.ExternalMessage as plain strings and forwards them to the Exchange Online provider, which uses Set-MailboxAutoReplyConfiguration.

Two practical gaps remain:

  1. No explicit support contract for formatted messages
    Users cannot tell from IdLE docs/contracts whether HTML is allowed and what the expectations are.

  2. Idempotency can become unstable when formatting is used
    Exchange may normalize or wrap the message content (for example by adding HTML/body wrappers or normalizing line endings).
    If IdLE compares message strings naively, workflows may report Changed = $true on every run even if the effective message did not change.

This makes formatted auto-replies hard to use reliably in repeatable lifecycle workflows.

Exchange Online Capability Check

Exchange Online supports formatted automatic reply messages via the Exchange Online PowerShell cmdlet Set-MailboxAutoReplyConfiguration.
The -InternalMessage and -ExternalMessage values are stored/handled as HTML on the server side (plain text input is typically wrapped/normalized into HTML).
This means IdLE can treat HTML as a supported input, but must handle server-side canonicalization to keep idempotency stable.

Proposed Solution

A) Extend the step contract to support message formats (data-only)

Add explicit, data-only keys to With.Config for message format and semantics:

  • MessageFormat: 'Text' | 'Html' (optional, default: 'Text')
  • InternalMessage: string (optional)
  • ExternalMessage: string (optional)

Rules:

  • When MessageFormat = 'Html', messages are treated as HTML markup and passed through without modification.
  • When MessageFormat = 'Text', messages are treated as plain text.
  • No ScriptBlocks allowed (keep existing data-only validation).

B) Provider-side normalization for stable idempotency

In IdLE.Provider.ExchangeOnline, implement a normalization routine used for idempotency comparison.

Suggested approach:

  • Add a private helper (example name): Normalize-IdleExchangeOnlineAutoReplyMessage
  • Normalize differences that Exchange commonly introduces (this can happen for both 'Text' and 'Html' inputs, because Exchange may persist/return HTML):
    • line endings (\r\n vs \n)
    • surrounding <html>, <head>, <body> wrappers
    • benign whitespace differences
  • Compare normalized current vs desired messages when deciding whether a change is required.

Important constraints:

  • Do not attempt to sanitize HTML (that would be surprising and could break intended formatting).
  • Keep normalization deterministic and minimal (only address known canonicalization effects).

C) Documentation + examples

Update docs to make formatted messages discoverable and safe to use:

  • docs/reference/steps/step-mailbox-ensure-out-of-office.md

    • Describe MessageFormat
    • Provide a formatted message example (HTML)
    • Mention idempotency normalization behavior
  • docs/reference/providers/provider-exchangeonline.md

    • Add a short example snippet showing a formatted (HTML) internal/external message.
  • Add or update an example workflow under examples/workflows/templates/ that demonstrates a formatted OOF message (for example with a link to Service Desk).

D) Tests

Add tests that prevent regressions:

  1. Step unit tests (Mailbox module)

    • Validates MessageFormat accepted values
    • Validates ScriptBlocks rejected inside Config (existing rule)
  2. ExchangeOnline provider unit tests (mock adapter)

    • Ensure normalization prevents repeated changes:
      • Desired message is a plain HTML string
      • Current message returned by adapter is the same content but wrapped/normalized
      • Provider reports Changed = $false
  3. If docs are generated: regenerate step reference and ensure the step page contains the new inputs.

Alternatives Considered

  1. Document "HTML works" without changing code
    Rejected because idempotency drift would still cause noisy plans and repeated updates in practice.

  2. Add a separate step EnsureOutOfOfficeHtml
    Rejected because it multiplies step types; a single step with an explicit MessageFormat is clearer.

  3. Convert Markdown to HTML inside IdLE
    Rejected because it adds complexity and a new transformation layer. Users can provide HTML directly.

Impact

  • Does this affect existing workflows?
    No breaking changes intended. Default behavior remains MessageFormat = 'Text'.

  • Any backward compatibility concerns?
    None expected ...


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 3 commits February 10, 2026 15:50
Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
Copilot AI changed the title [WIP] Add support for formatted HTML auto-reply messages in ExchangeOnline Add HTML message format support with stable idempotency for ExchangeOnline OOF Feb 10, 2026
Copilot AI requested a review from blindzero February 10, 2026 15:55
Copy link
Copy Markdown
Owner

@blindzero blindzero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot Grundsätzlich schon o.k. Gerade für die Template Definition wäre es gut, externe Dateien nutzen zu können um nicht alles in der Workflow Definition selbst angeben zu müssen (wird schnell recht lang).

Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Feb 10, 2026

@copilot Grundsätzlich schon o.k. Gerade für die Template Definition wäre es gut, externe Dateien nutzen zu können um nicht alles in der Workflow Definition selbst angeben zu müssen (wird schnell...

Ich habe die Unterstützung für externe Template-Dateien hinzugefügt. Sie können jetzt @{ FromFile = 'pfad/zur/datei.html' } verwenden, um HTML-Nachrichten aus externen Dateien zu laden.

Beispiel:

Config = @{
    Mode            = 'Enabled'
    MessageFormat   = 'Html'
    InternalMessage = @{ FromFile = './templates/oof-internal.html' }
    ExternalMessage = @{ FromFile = './templates/oof-external.html' }
}

Features:

  • Absolute oder relative Pfade (relativ zum aktuellen Verzeichnis)
  • Template-Platzhalter ({{...}}) funktionieren sowohl im Dateipfad als auch im Dateiinhalt
  • UTF-8 Kodierung
  • Validierung zur Planungszeit

Commit: 0167410

Siehe auch das neue Beispiel-Workflow exo-leaver-mailbox-offboarding-external-templates.psd1 und die Dokumentation in docs/reference/providers/provider-exchangeonline.md.

Copilot AI changed the title Add HTML message format support with stable idempotency for ExchangeOnline OOF Add HTML message format support with stable idempotency and external file loading for ExchangeOnline OOF Feb 10, 2026
Copilot AI requested a review from blindzero February 10, 2026 17:41
Copy link
Copy Markdown
Owner

@blindzero blindzero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot is it ensured that the external template file is also supporting the template substitution for dynamic content insert

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Feb 10, 2026

@copilot is it ensured that the external template file is also supporting the template substitution for dynamic content insert

Yes, template substitution for dynamic content is fully supported within external files.

How it works:

  1. File content is loaded as UTF-8
  2. Template placeholders ({{...}}) in the file are resolved against the Request object
  3. The resolved content is then used as the message value

Example file (templates/oof-internal.html):

<p>Contact <a href="mailto:{{Request.DesiredState.Manager.Mail}}">{{Request.DesiredState.Manager.DisplayName}}</a></p>

At planning time, placeholders are replaced with actual values from the request (e.g., 'jmanager@contoso.com' and 'Jane Manager').

This is validated by the test "resolves template placeholders within loaded file content" in tests/Core/Resolve-IdleWorkflowTemplates.Tests.ps1 (lines 565-585).

@blindzero blindzero marked this pull request as ready for review February 10, 2026 19:28
Copilot AI review requested due to automatic review settings February 10, 2026 19:28
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 01674108c9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds first-class support for HTML-formatted Exchange Online Out of Office (OOF) messages in IdLE, addressing idempotency drift caused by Exchange server-side canonicalization, and introduces a @{ FromFile = '...' } workflow template pattern for loading message bodies from external files during planning.

Changes:

  • Extended the OOF step contract with optional With.Config.MessageFormat (Text/Html) and validation.
  • Implemented ExchangeOnline provider message normalization for idempotent comparisons and updated idempotency check flow.
  • Added FromFile support to template resolution plus tests, docs, and example workflows/templates.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/fixtures/templates/oof-internal.html Adds HTML template fixture for FromFile/template-resolution tests.
tests/fixtures/templates/oof-external.html Adds HTML template fixture for FromFile/template-resolution tests.
tests/Steps/Invoke-IdleStepMailboxOutOfOfficeEnsure.Tests.ps1 Adds unit tests for MessageFormat validation.
tests/Providers/ExchangeOnlineProvider.Tests.ps1 Adds idempotency tests for HTML wrapper + line-ending normalization and direct tests for the normalization helper.
tests/Core/Resolve-IdleWorkflowTemplates.Tests.ps1 Adds unit tests for the new @{ FromFile = '...' } pattern (path templating + content templating + errors).
src/IdLE.Steps.Mailbox/Public/Invoke-IdleStepMailboxOutOfOfficeEnsure.ps1 Documents and validates With.Config.MessageFormat.
src/IdLE.Provider.ExchangeOnline/Public/New-IdleExchangeOnlineProvider.ps1 Uses message normalization in idempotency comparisons for OOF messages.
src/IdLE.Provider.ExchangeOnline/Private/Normalize-IdleExchangeOnlineAutoReplyMessage.ps1 Introduces normalization helper for stable message comparisons.
src/IdLE.Core/Private/Resolve-IdleWorkflowTemplates.ps1 Implements FromFile template pattern (UTF-8 read during planning + placeholder substitution).
examples/workflows/templates/templates/oof-leaver-internal.html Adds example internal HTML template file.
examples/workflows/templates/templates/oof-leaver-external.html Adds example external HTML template file.
examples/workflows/templates/exo-leaver-mailbox-offboarding.psd1 Updates example workflow to use inline HTML messages.
examples/workflows/templates/exo-leaver-mailbox-offboarding-external-templates.psd1 Adds example workflow using FromFile to load external HTML templates.
docs/reference/steps/step-mailbox-ensure-out-of-office.md Regenerated step reference to include MessageFormat.
docs/reference/providers/provider-exchangeonline.md Adds provider documentation and examples for HTML OOF and FromFile.

Repository owner deleted a comment from chatgpt-codex-connector Bot Feb 10, 2026
Comment thread src/IdLE.Core/Private/Resolve-IdleWorkflowTemplates.ps1 Outdated
Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
Copilot AI changed the title Add HTML message format support with stable idempotency and external file loading for ExchangeOnline OOF Add HTML message format support with stable idempotency for ExchangeOnline OOF Feb 10, 2026
Copilot AI requested a review from blindzero February 10, 2026 19:50
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@blindzero
Copy link
Copy Markdown
Owner

@copilot apply changes based on the comments in this thread

Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Feb 10, 2026

@copilot apply changes based on the comments in this thread

Applied the suggested changes from the review thread (commit dfc3e3e):

Removed short-circuit checks in the idempotency logic - all configuration properties (Mode, InternalMessage, ExternalMessage, ExternalAudience, Start/End dates) are now checked independently. Previously, the -not $changed conditions meant that once any difference was found, subsequent checks were skipped.

Impact: This ensures that all fields are properly evaluated for drift detection, which aligns with the PR description's claim of "independent checks" and fixes the issue where ExternalAudience changes could be masked by earlier checks.

All 27 provider tests still pass with this change.

Repository owner deleted a comment from Copilot AI Feb 10, 2026
Repository owner deleted a comment from Copilot AI Feb 10, 2026
Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
Copilot AI requested a review from blindzero February 10, 2026 20:16
Repository owner deleted a comment from chatgpt-codex-connector Bot Feb 10, 2026
Repository owner deleted a comment from Copilot AI Feb 10, 2026
Comment thread src/IdLE.Core/Private/Resolve-IdleWorkflowTemplates.ps1 Outdated
@blindzero blindzero merged commit d89dc43 into main Feb 10, 2026
9 checks passed
@blindzero blindzero deleted the copilot/support-oof-formatted-messages branch February 15, 2026 19:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ExchangeOnline OOF support formatted HTML auto-reply messages with stable idempotency

3 participants