Skip to content

Latest commit

Β 

History

History
480 lines (450 loc) Β· 22.2 KB

File metadata and controls

480 lines (450 loc) Β· 22.2 KB

M365 Digest Email System - Architecture

System Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    M365 Digest Email System                     β”‚
β”‚                                                                 β”‚
β”‚  Professional HTML email sending with inline images & OAuth2   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Component Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   β”‚      β”‚                      β”‚      β”‚                  β”‚
β”‚  CSV Data File    │─────▢│   PowerShell         │─────▢│  Microsoft 365   β”‚
β”‚  (Recipients)     β”‚      β”‚   Control Script     β”‚      β”‚  Exchange Online β”‚
β”‚                   β”‚      β”‚                      β”‚      β”‚                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                      β”‚
                                      β”‚ Uses
                                      β–Ό
                           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                           β”‚                      β”‚
                           β”‚  M365DigestEmail     β”‚
                           β”‚  PowerShell Module   β”‚
                           β”‚                      β”‚
                           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                      β”‚
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚                 β”‚                 β”‚
                    β–Ό                 β–Ό                 β–Ό
         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
         β”‚  Authentication β”‚ β”‚   Template   β”‚ β”‚  SMTP Sending    β”‚
         β”‚     Module      β”‚ β”‚   Processor  β”‚ β”‚  with Batching   β”‚
         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                    β”‚                 β”‚                 β”‚
         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”        β”‚        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
         β”‚                   β”‚        β”‚        β”‚                   β”‚
         β–Ό                   β–Ό        β–Ό        β–Ό                   β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Basic  β”‚         β”‚ OAuth2 β”‚ β”‚ HTML β”‚ β”‚ Inline β”‚      β”‚Checkpointβ”‚
    β”‚  Auth  β”‚         β”‚  (AAD) β”‚ β”‚ Body β”‚ β”‚ Images β”‚      β”‚   File   β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Data Flow Diagram

START
  β”‚
  β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Load Configuration              β”‚
β”‚ - CSV path                      β”‚
β”‚ - HTML template                 β”‚
β”‚ - Inline images                 β”‚
β”‚ - SMTP settings                 β”‚
β”‚ - Auth method                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                  β”‚
                  β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Validate Configuration          β”‚
β”‚ - Check files exist             β”‚
β”‚ - Verify image CIDs             β”‚
β”‚ - Test authentication           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                  β”‚
                  β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Load Checkpoint File            β”‚
β”‚ (Resume from previous run)      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                  β”‚
                  β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Import & Parse CSV              β”‚
β”‚ - Filter already-sent emails    β”‚
β”‚ - Build recipient objects       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                  β”‚
                  β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ For Each Batch (20 emails):     β”‚
β”‚                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚ For Each Recipient:      β”‚   β”‚
β”‚  β”‚                          β”‚   β”‚
β”‚  β”‚  1. Process HTML         β”‚   β”‚
β”‚  β”‚     template             β”‚   β”‚
β”‚  β”‚  2. Replace placeholders β”‚   β”‚
β”‚  β”‚  3. Create AlternateView β”‚   β”‚
β”‚  β”‚  4. Embed inline images  β”‚   β”‚
β”‚  β”‚  5. Add attachments      β”‚   β”‚
β”‚  β”‚  6. Send via SMTP        β”‚   β”‚
β”‚  β”‚  7. Retry if failed (3x) β”‚   β”‚
β”‚  β”‚  8. Write to checkpoint  β”‚   β”‚
β”‚  β”‚                          β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                                 β”‚
β”‚  Wait for batch window (3 min) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                  β”‚
                  β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Campaign Complete               β”‚
β”‚ - Display statistics            β”‚
β”‚ - Close connections             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  β”‚
  β–Ό
END

Module Function Hierarchy

M365DigestEmailModule.psm1
β”‚
β”œβ”€β”€ Get-EmailAuthenticationCredential
β”‚   β”‚
β”‚   β”œβ”€β”€ Basic Authentication
β”‚   β”‚   └── PSCredential (username/password)
β”‚   β”‚
β”‚   └── OAuth2 Authentication
β”‚       β”œβ”€β”€ Token Endpoint Request
β”‚       β”œβ”€β”€ Client Credentials Flow
β”‚       └── PSCredential (username/token)
β”‚
β”œβ”€β”€ Get-ProcessedHtmlTemplate
β”‚   β”‚
β”‚   β”œβ”€β”€ Load HTML file
β”‚   β”œβ”€β”€ HTML-encode values (XSS protection)
β”‚   └── Replace placeholders
β”‚
β”œβ”€β”€ New-EmailAlternateViewWithImages
β”‚   β”‚
β”‚   β”œβ”€β”€ Create AlternateView object
β”‚   β”œβ”€β”€ For each inline image:
β”‚   β”‚   β”œβ”€β”€ Create LinkedResource
β”‚   β”‚   β”œβ”€β”€ Set ContentType (image/png, image/jpeg)
β”‚   β”‚   β”œβ”€β”€ Set ContentId (CID reference)
β”‚   β”‚   └── Add to AlternateView
β”‚   └── Return AlternateView
β”‚
β”œβ”€β”€ Send-HtmlEmail
β”‚   β”‚
β”‚   β”œβ”€β”€ Create MailMessage object
β”‚   β”œβ”€β”€ Set To, From, Subject, BCC
β”‚   β”œβ”€β”€ Create AlternateView with images
β”‚   β”œβ”€β”€ Add attachments
β”‚   β”œβ”€β”€ Create SmtpClient
β”‚   β”œβ”€β”€ Set credentials & SSL
β”‚   β”œβ”€β”€ Send email
β”‚   └── Dispose objects (cleanup)
β”‚
└── Send-BulkHtmlEmail
    β”‚
    β”œβ”€β”€ Load checkpoint file
    β”œβ”€β”€ Filter pending recipients
    β”œβ”€β”€ For each batch:
    β”‚   β”œβ”€β”€ Process template per recipient
    β”‚   β”œβ”€β”€ Call Send-HtmlEmail
    β”‚   β”œβ”€β”€ Retry logic (exponential backoff)
    β”‚   β”œβ”€β”€ Update checkpoint file
    β”‚   └── Wait for window interval
    └── Return statistics

Authentication Flow

Basic Authentication

User Script
    β”‚
    β–Ό
Get-EmailAuthenticationCredential
    β”‚
    β”œβ”€β”€ Convert password to SecureString
    β”‚
    β”œβ”€β”€ Create PSCredential object
    β”‚   (username + secure password)
    β”‚
    └── Return credential
          β”‚
          β–Ό
SmtpClient.Credentials = PSCredential
          β”‚
          β–Ό
    SMTP AUTH LOGIN
    (Base64 encoded credentials)
          β”‚
          β–Ό
    Office 365 validates
          β”‚
          β–Ό
    Authentication SUCCESS/FAIL

OAuth2 Authentication

User Script
    β”‚
    β–Ό
Get-EmailAuthenticationCredential
    β”‚
    β”œβ”€β”€ Build token request
    β”‚   - TenantId
    β”‚   - ClientId  
    β”‚   - ClientSecret
    β”‚   - Scope: outlook.office365.com/.default
    β”‚
    β”œβ”€β”€ POST to token endpoint
    β”‚   https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
    β”‚
    β”œβ”€β”€ Receive access token
    β”‚
    β”œβ”€β”€ Create PSCredential
    β”‚   (username + token as password)
    β”‚
    └── Return credential
          β”‚
          β–Ό
SmtpClient.Credentials = PSCredential
          β”‚
          β–Ό
    SMTP AUTH XOAUTH2
    (OAuth bearer token)
          β”‚
          β–Ό
    Azure AD validates token
          β”‚
          β–Ό
    Authentication SUCCESS/FAIL

Email Construction

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   MailMessage                      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                    β”‚
β”‚  Headers:                                          β”‚
β”‚    From: sender@example.com                        β”‚
β”‚    To: recipient@example.com                       β”‚
β”‚    Subject: Microsoft 365 Monthly Digest           β”‚
β”‚    BCC: admin@example.com                          β”‚
β”‚                                                    β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚          AlternateView (text/html)           β”‚ β”‚
β”‚  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚
β”‚  β”‚                                              β”‚ β”‚
β”‚  β”‚  <html>                                      β”‚ β”‚
β”‚  β”‚    <body>                                    β”‚ β”‚
β”‚  β”‚      ...HTML content...                      β”‚ β”‚
β”‚  β”‚      <img src="cid:logo1">                   β”‚ β”‚
β”‚  β”‚      <img src="cid:icon1">                   β”‚ β”‚
β”‚  β”‚    </body>                                   β”‚ β”‚
β”‚  β”‚  </html>                                     β”‚ β”‚
β”‚  β”‚                                              β”‚ β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
β”‚  β”‚  β”‚    LinkedResource (logo1)              β”‚ β”‚ β”‚
β”‚  β”‚  β”‚    - ContentId: logo1                  β”‚ β”‚ β”‚
β”‚  β”‚  β”‚    - ContentType: image/png            β”‚ β”‚ β”‚
β”‚  β”‚  β”‚    - TransferEncoding: Base64          β”‚ β”‚ β”‚
β”‚  β”‚  β”‚    - Data: [PNG bytes]                 β”‚ β”‚ β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚  β”‚                                              β”‚ β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
β”‚  β”‚  β”‚    LinkedResource (icon1)              β”‚ β”‚ β”‚
β”‚  β”‚  β”‚    - ContentId: icon1                  β”‚ β”‚ β”‚
β”‚  β”‚  β”‚    - ContentType: image/png            β”‚ β”‚ β”‚
β”‚  β”‚  β”‚    - TransferEncoding: Base64          β”‚ β”‚ β”‚
β”‚  β”‚  β”‚    - Data: [PNG bytes]                 β”‚ β”‚ β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚  β”‚                                              β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚                                                    β”‚
β”‚  Attachments:                                      β”‚
β”‚    β”œβ”€β”€ Manual.pdf (application/pdf)                β”‚
β”‚    └── Guide.pdf (application/pdf)                 β”‚
β”‚                                                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Batch Processing Flow

Batch 1 (emails 1-20)
β”œβ”€β”€ Email 1  ────────┐
β”œβ”€β”€ Email 2          β”‚
β”œβ”€β”€ Email 3          β”‚
β”œβ”€β”€ ...              β”‚  Sending Phase
β”œβ”€β”€ Email 19         β”‚  (~30-60 seconds)
β”œβ”€β”€ Email 20  β”€β”€β”€β”€β”€β”€β”€β”˜
└── WAIT 3 minutes ◄─── Rate Limiting Window

Batch 2 (emails 21-40)
β”œβ”€β”€ Email 21 ────────┐
β”œβ”€β”€ Email 22         β”‚
β”œβ”€β”€ ...              β”‚  Sending Phase
β”œβ”€β”€ Email 39         β”‚
β”œβ”€β”€ Email 40  β”€β”€β”€β”€β”€β”€β”€β”˜
└── WAIT 3 minutes

... Continue for all batches

Error Handling & Retry Logic

Send Email
    β”‚
    β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Attempt 1         β”‚
β”‚   (immediate)       β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚
   β”Œβ”€β”€β”€β”΄β”€β”€β”€β”
   β”‚ OK?   β”‚
   β””β”€β”€β”€β”¬β”€β”€β”€β”˜
       β”‚
   β”Œβ”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚ Success β†’ Write checkpointβ”‚
   β”‚ Fail β†’ Wait 2s             β”‚
   β””β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚
       β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Attempt 2         β”‚
β”‚   (after 2s)        β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚
   β”Œβ”€β”€β”€β”΄β”€β”€β”€β”
   β”‚ OK?   β”‚
   β””β”€β”€β”€β”¬β”€β”€β”€β”˜
       β”‚
   β”Œβ”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚ Success β†’ Write checkpointβ”‚
   β”‚ Fail β†’ Wait 4s             β”‚
   β””β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚
       β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Attempt 3         β”‚
β”‚   (after 4s)        β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚
   β”Œβ”€β”€β”€β”΄β”€β”€β”€β”
   β”‚ OK?   β”‚
   β””β”€β”€β”€β”¬β”€β”€β”€β”˜
       β”‚
   β”Œβ”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚ Success β†’ Write checkpointβ”‚
   β”‚ Fail β†’ Log permanent error β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Checkpoint System

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚          Email Campaign Execution                 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                   β”‚
β”‚  Email 1 β†’ Success ──┬─→ checkpoint.txt          β”‚
β”‚  Email 2 β†’ Success ───     user1@example.com     β”‚
β”‚  Email 3 β†’ Success ───     user2@example.com     β”‚
β”‚  Email 4 β†’ FAIL      β”‚     user3@example.com     β”‚
β”‚  Email 5 β†’ Success ───     user5@example.com     β”‚
β”‚  Email 6 β†’ Success β”€β”€β”˜                            β”‚
β”‚                                                   β”‚
β”‚  ╔════════════════════════════════════╗           β”‚
β”‚  β•‘  SCRIPT INTERRUPTED (Ctrl+C)      β•‘           β”‚
β”‚  β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•           β”‚
β”‚                                                   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”             β”‚
β”‚  β”‚  Restart script                 β”‚             β”‚
β”‚  β”‚  ↓                               β”‚             β”‚
β”‚  β”‚  Load checkpoint.txt             β”‚             β”‚
β”‚  β”‚  ↓                               β”‚             β”‚
β”‚  β”‚  Skip: user1, user2, user3, user5β”‚             β”‚
β”‚  β”‚  ↓                               β”‚             β”‚
β”‚  β”‚  Resume: Email 4, 7, 8, 9...    β”‚             β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜             β”‚
β”‚                                                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

File Dependencies

Project Root
β”‚
β”œβ”€β”€ M365DigestEmailModule.psm1         ◄─── Core module (REQUIRED)
β”‚
β”œβ”€β”€ M365_Digest_Template.htm           ◄─── HTML template (REQUIRED)
β”‚
β”œβ”€β”€ Send-M365Digest-BasicAuth.ps1     ◄─── Example script (Basic)
β”œβ”€β”€ Send-M365Digest-OAuth.ps1         ◄─── Example script (OAuth)
β”œβ”€β”€ Test-M365DigestConfig.ps1         ◄─── Configuration validator
β”‚
β”œβ”€β”€ recipients.csv                     ◄─── Recipient data (USER PROVIDED)
β”‚
β”œβ”€β”€ Images/                            ◄─── Inline images (USER PROVIDED)
β”‚   β”œβ”€β”€ datagroup_logo.png
β”‚   β”œβ”€β”€ m365_icon.png
β”‚   β”œβ”€β”€ exchange_icon.png
β”‚   └── sharepoint_icon.png
β”‚
β”œβ”€β”€ Attachments/                       ◄─── PDF files (USER PROVIDED)
β”‚   β”œβ”€β”€ Manual1.pdf
β”‚   β”œβ”€β”€ Manual2.pdf
β”‚   └── Manual3.pdf
β”‚
└── Logs/                              ◄─── Runtime files (AUTO GENERATED)
    β”œβ”€β”€ smtp_send_checkpoint.txt
    └── campaign_YYYYMMDD_HHmmss.log

Performance Characteristics

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Campaign Size: 1000 recipients                     β”‚
β”‚  Batch Size: 20 emails                              β”‚
β”‚  Window Interval: 3 minutes                         β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                     β”‚
β”‚  Total Batches: 50                                  β”‚
β”‚  Total Time: ~150 minutes (2.5 hours)               β”‚
β”‚                                                     β”‚
β”‚  Time Breakdown:                                    β”‚
β”‚    - Sending: ~50 minutes (1 min/batch)             β”‚
β”‚    - Waiting: ~147 minutes (3 min Γ— 49 intervals)   β”‚
β”‚    - Retries: ~3 minutes (if 1% failure rate)       β”‚
β”‚                                                     β”‚
β”‚  Throughput: ~6.7 emails/minute (sustainable)       β”‚
β”‚                                                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Security Layers

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  Security Measures                  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                     β”‚
β”‚  1. HTML Encoding                                   β”‚
β”‚     └─→ Prevents XSS injection attacks              β”‚
β”‚                                                     β”‚
β”‚  2. Secure Credentials                              β”‚
β”‚     β”œβ”€β†’ PSCredential with SecureString              β”‚
β”‚     └─→ OAuth tokens (short-lived)                  β”‚
β”‚                                                     β”‚
β”‚  3. TLS/SSL Encryption                              β”‚
β”‚     └─→ SMTP over TLS (port 587)                    β”‚
β”‚                                                     β”‚
β”‚  4. Application Permissions                         β”‚
β”‚     └─→ OAuth: Mail.Send only (least privilege)     β”‚
β”‚                                                     β”‚
β”‚  5. Input Validation                                β”‚
β”‚     β”œβ”€β†’ File path validation                        β”‚
β”‚     β”œβ”€β†’ Email address format check                  β”‚
β”‚     └─→ Content size limits                         β”‚
β”‚                                                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Architecture Documentation v1.0.0 | Built for Microsoft 365 Enterprise Email Campaigns