Outlook Automation: Sync & Archiving

SOP Alignment: SOP-006 (Shipping & Logistics) Constitution Compliance: Articles I (Backbone as source of truth), II (Stateless execution), III (Auditability), IV (SOP alignment) Last Updated: 2026-03-01 Status: Active

This document describes all automated processes for managing Outlook emails, attachments, calendar events, and project archiving within the MDL Ops Engine.


1. Overview

The Outlook automation suite consists of Python scripts and n8n workflows that reduce manual overhead in project organization, document management, and financial ingestion. All scripts are idempotent (safe to re-run) and use state files to prevent duplicate processing.


2. Core Utility Scripts

2.1 Email Organization (organize_outlook_emails.py)

2.2 Attachment Sync (sync_outlook_attachments.py)

2.3 Project Archiving (archive_old_projects.py)

2.4 Outlook Tagger (outlook_tagger.py)


3. Running Utility Scripts

These scripts can be run manually from the terminal or scheduled via Task Scheduler:

# Sync attachments
python scripts/sync_outlook_attachments.py

# Organize emails by project
python scripts/organize_outlook_emails.py

# Archive old projects
python scripts/archive_old_projects.py

4. SOP-006 n8n Automation Workflows

All n8n workflows follow the naming convention: [Human Description] (SOP-006-[SUBTAG])

See the full lifecycle map: SOP006_n8n_Automation_Map.md

4.1 Package Tracking Ingest (SOP-006-TRACKING) โœ… LIVE

Field Value
Trigger Outlook Trigger โ€” polls every 30 min for carrier shipment status emails
Actions Sends to /webhook/ingest-tracking โ†’ logs to Supabase, sends delivery notifications
SOP Reference SOP-006 ยง5.6, ยง6.1
n8n Workflow Name Package Tracking Ingest (SOP-006-TRACKING)

4.2 Shipping Invoice Ingest (SOP-006-INVOICING) โœ… LIVE

Field Value
Trigger Outlook Trigger โ€” polls every 30 min for carrier invoice emails (TotalShip, UPS, FedEx, DHL, Purolator) + hourly folder sweep
Actions PDF extraction โ†’ Gemini AI parsing โ†’ QBO Vendor lookup โ†’ QBO Bill creation โ†’ Supabase log โ†’ 2 calendar events โ†’ email notification โ†’ email tagged
SOP Reference SOP-006 ยง5.4.2
n8n Workflow Name Shipping Invoice Ingest (SOP-006-INVOICING)
n8n File n8n-docker/SOP006_Shipping_Invoice_Monitor.json
Backend Script scripts/ingest_shipping_invoice.py
API Endpoints POST /webhook/ingest-invoice, POST /webhook/scan-invoice-folders

How the Invoice Pipeline Works

  1. n8n trigger polls Inbox every 30 minutes for carrier invoice emails.
  2. Attachment fetch โ€” retrieves PDF invoice via Microsoft Graph API.
  3. Intelligent Processor (/webhook/ingest-invoice) runs the background pipeline:
  4. Gemini AI extracts: vendor name, invoice #, total, date, PO reference, currency.
  5. Vendor matched in QBO, QBO Bill created (Net 30 default; Net 21 for UPS/FedEx).
  6. Invoice logged to shipping_invoices Supabase table.
  7. ๐Ÿ“ฆ Bill Logged calendar event created on invoice date.
  8. โš ๏ธ Bill Due calendar reminder created on due date.
  9. Email notification sent to MDL_NOTIFY_EMAIL.
  10. Source email tagged "QBO Bill Created" (๐Ÿ”ด Red category).
  11. Hourly folder sweep (/webhook/scan-invoice-folders) catches any invoices that moved before the real-time trigger.

Manual Run Commands

# Dry run โ€” scan inbox but make NO writes (safe to test anytime)
docker exec intelligent-processor python /app/scripts/ingest_shipping_invoice.py --dry-run

# Live run โ€” Inbox + Logistics folders (last 30 days)
docker exec intelligent-processor python /app/scripts/ingest_shipping_invoice.py

# Custom folder + lookback window
docker exec intelligent-processor python /app/scripts/ingest_shipping_invoice.py \
  --folders Inbox "03_Operations/Logistics/TotalShip" --days 7

Supabase: shipping_invoices Table

โœ… Migration already applied as of 2026-03-01.

CREATE TABLE IF NOT EXISTS public.shipping_invoices (
    id                BIGSERIAL PRIMARY KEY,
    invoice_number    TEXT UNIQUE,
    vendor_name       TEXT,
    invoice_date      DATE,
    po_reference      TEXT,
    currency          TEXT DEFAULT 'CAD',
    subtotal          NUMERIC(12, 2),
    tax               NUMERIC(12, 2),
    shipping_charge   NUMERIC(12, 2),
    total             NUMERIC(12, 2),
    qbo_bill_id       TEXT,
    source            TEXT DEFAULT 'outlook_auto',
    processed_at      TIMESTAMPTZ DEFAULT NOW()
);

Required Environment Variables

Variable Value Description
GEMINI_API_KEY AIzaSy... AI invoice data extraction (already set โœ…)
MDL_NOTIFY_EMAIL bernard.cresencia@mdlcommunications.com Invoice notification recipient (already set โœ…)
CLIENT_ID โ€” Microsoft Graph API auth (already set โœ…)
CLIENT_SECRET โ€” Microsoft Graph API auth (already set โœ…)
TENANT_ID โ€” Microsoft Graph API auth (already set โœ…)
SUPABASE_URL โ€” Supabase logging (already set โœ…)
SUPABASE_KEY โ€” Supabase logging (already set โœ…)

5. Best Practices & Constitution Compliance

5.1 Idempotency & Deduplication (Article II โ€” Stateless Execution)

5.2 Auditability (Article III โ€” Universal Auditability)

5.3 AI Confidence & Human Oversight (Article V โ€” AI & Intelligence)

5.4 SOP Alignment (Article IV)

5.5 Token & Auth Management


6. Troubleshooting

Symptom Likely Cause Fix
O365 not authenticated Token expired Refresh OAuth token via TokenManager
Gemini extraction failed (400) Prompt too long or malformed Check invoice PDF โ€” use --dry-run to inspect
QBO Vendor not found Vendor name in invoice doesn't match QBO DisplayName Add/alias vendor in QBO; update _guess_vendor_from_sender()
Supabase upsert failed Duplicate invoice_number with conflicting data Review shipping_invoices table; delete stale row if needed
Container restarting Python path issue Ensure ENV PYTHONPATH=/app in Dockerfile.processor
n8n workflow not triggering Outlook credential expired in n8n Re-authenticate the Microsoft Outlook credential in n8n settings