Skip to content

Instantly share code, notes, and snippets.

@stormwild
Created March 3, 2026 16:14
Show Gist options
  • Select an option

  • Save stormwild/76e66405489f9f04abb2ad3f1999bbfa to your computer and use it in GitHub Desktop.

Select an option

Save stormwild/76e66405489f9f04abb2ad3f1999bbfa to your computer and use it in GitHub Desktop.

Tamper-Evident Reply Validation

Using Microsoft Graph API + Exchange Online (Enterprise)

Problem Statement

When sending outbound emails through Microsoft Graph (Exchange Online), we want to guarantee that:

An inbound email is a legitimate reply to the exact outbound message we sent — not a spoofed or fabricated email.

Traditional email headers (From, In-Reply-To, References) can be forged. DKIM/SPF/DMARC validate domain authenticity — but do not cryptographically bind an inbound reply to a specific outbound message.

Microsoft Graph does not provide a built-in feature that verifies “this inbound message is a guaranteed reply to outbound message X.”

Therefore, we implement our own tamper-evident reply binding mechanism.


High-Level Solution

Use:

  1. Exchange Online Plus Addressing
  2. Signed reply tokens
  3. Graph sendMail (with replyTo)
  4. Graph delta query for inbound processing

This creates a cryptographically verifiable link between outbound and inbound messages.


Architecture Overview

Internal API
    ↓
Microsoft Graph sendMail
    ↓
Exchange Online
    ↓
Client replies
    ↓
Shared Mailbox (replies@contoso.com)
    ↓
Inbound Processor (Graph delta query)
    ↓
Token validation + outbound binding

Core Concept: Signed Reply Token

Instead of trusting email headers, we embed a cryptographically signed token into the replyTo address using plus addressing.

Example Outbound

From: notifications@contoso.com
Reply-To: replies+<signedToken>@contoso.com

When the client clicks “Reply,” the message is sent to:

replies+<signedToken>@contoso.com

Because Exchange Online supports plus addressing, the email is delivered to:

replies@contoso.com

But the token remains visible in the recipient address for validation.


Token Design

Token Payload (example)

{
  "version": 1,
  "outboundId": "6f2d4cbb-0a62-4fcb-b4a5-1f5e44b6b112",
  "recipientHash": "sha256(email)",
  "tenantId": "enterprise-01",
  "issuedAt": 1700000000,
  "expiresAt": 1700003600,
  "nonce": "random-128-bit"
}

Signing

Recommended:

  • HMAC-SHA256 (shared secret)
  • or Ed25519 (public/private key pair)

Then encode:

Base64UrlEncode(payload + signature)

The resulting token must be URL-safe (no +, /, or = characters).


Outbound Implementation (Microsoft Graph)

Step 1 — Generate Token

  • Create outboundId
  • Store outbound record in database
  • Create token payload
  • Sign token

Step 2 — Send Mail with replyTo

Example JSON body for POST /users/{id}/sendMail:

{
  "message": {
    "subject": "Your Case Update",
    "body": {
      "contentType": "HTML",
      "content": "<p>Your case has been updated.</p>"
    },
    "toRecipients": [
      {
        "emailAddress": {
          "address": "client@example.com"
        }
      }
    ],
    "replyTo": [
      {
        "emailAddress": {
          "address": "replies+SIGNED_TOKEN@contoso.com"
        }
      }
    ]
  }
}

Microsoft Graph supports setting replyTo on messages.


Inbound Processing (Graph Delta Query)

Monitor the shared mailbox:

GET /users/replies@contoso.com/mailFolders('Inbox')/messages/delta

For each new message:

  1. Extract recipient address

  2. Parse token from replies+<token>@contoso.com

  3. Decode and validate token

  4. Verify:

    • Signature valid
    • Not expired
    • OutboundId exists
    • Nonce unused (optional replay protection)
  5. Bind inbound message to outbound record

If validation fails → mark as Unverified Reply


Validation Rules

An inbound message is marked as Verified Reply only if:

  • Token is present
  • Signature is valid
  • Token not expired
  • Token maps to stored outbound record
  • (Optional) inbound From matches expected recipient
  • (Optional) nonce not previously used

Optional Fallback: Body Token

As a secondary safeguard, embed a reference line in the body:

Reference: ACME-REPLY:v1.<token>

On inbound:

  • Scan body for token
  • Validate same way

This protects against scenarios where users manually edit the recipient address.


Why Not Just Use Email Headers?

Mechanism Protects Against Spoofing? Binds to Specific Outbound?
In-Reply-To ❌ No ❌ No
References ❌ No ❌ No
DKIM/SPF/DMARC ✅ Domain validation ❌ Not per-message binding
Custom Headers ⚠ Sometimes preserved ❌ Not reliable
Signed Reply Token ✅ Yes ✅ Yes

Enterprise Hardening Recommendations

  • Use key rotation (include kid in token header)
  • Store token hash instead of full token
  • Enforce short expiration window
  • Enable replay detection
  • Log validation events
  • Rate limit per token
  • Use tenant-scoped signing keys
  • Monitor plus addressing configuration in Exchange Online

Security Properties Achieved

With this approach:

✔ You can prove the reply channel was minted by your system ✔ You can prove it references a specific outbound message ✔ Spoofed emails cannot generate valid tokens ✔ Forwarded replies still validate ✔ Validation is deterministic and auditable


What Microsoft Graph Does NOT Provide

  • No built-in “verified reply to this outbound” flag
  • No automatic cryptographic reply binding
  • No built-in anti-reply spoofing mechanism

Graph provides:

  • replyTo
  • Custom headers (limited use)
  • Message delta queries
  • Mailbox monitoring

The cryptographic binding must be implemented in your application layer.


Summary

In an enterprise environment using Microsoft Graph:

The correct pattern for tamper-evident reply validation is:

Signed reply tokens embedded in replyTo using Exchange Online plus addressing, validated on inbound via Graph.

This provides strong cryptographic assurance that an inbound email is a legitimate reply to a specific outbound message sent by your system.


If desired, this document can be extended with:

  • C# sample token generation/validation code
  • Ed25519 implementation example
  • Azure Key Vault key management pattern
  • Multi-tenant architecture variant
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment