Power Automate — Outlook archive to SharePoint

Archive new Inbox messages to Leo/Agentic SharePoint with:

Updated

  • EML files: outlook/YYYY/MM/DD/{yyyyMMdd-HHmmss}-{safeMessageId}.eml
  • Index: outlook/messages.xlsx (Excel table Messages)

LCDL supplies conventions and read-only verification; Power Automate performs writes.

Prerequisites

  1. Access to https://softserveinc.sharepoint.com/sites/EnterpriseDelivery-Leo and folder Shared Documents/Leo/Agentic.
  2. Power Automate environment (e.g. Default-6c51c659-9d52-41af-81f7-dde16380e813).
  3. Bootstrap index file:
cd forge-lcdl
pip install -e '.[files]'
python3 scripts/bootstrap-messages-xlsx.py

Upload src/forge_lcdl/power_automate/templates/messages.xlsx to:

Shared Documents/Leo/Agentic/outlook/messages.xlsx

Or run the one-time operator script (creates outlook/ folder if needed):

FORGE_LCDL_PLAYWRIGHT_LIVE=1 python3 scripts/upload-outlook-messages-bootstrap.py

Flow: LCDL-Outlook-Archive-To-Agentic

LCDL-assisted build (live Edge CDP)

One command — opens a new flow designer, runs trigger → variables → export → SharePoint → Excel → save/On (no per-block replies):

FORGE_LCDL_PLAYWRIGHT_LIVE=1 python3 scripts/pa-build-outlook-archive-flow.py --all --json

Each run uses a timestamped flow name (LCDL-Outlook-Archive-To-Agentic-YYYYMMDD-HHMM) unless you pass --flow-name. Edge CDP must be running with a signed-in make.powerautomate.com tab.

Window focus: LCDL does not steal OS focus by default. CDP focus emulation keeps the PA tab “active” for the designer while you work in another app. Leave FORGE_LCDL_CDP_BRING_TO_FRONT unset (or 0). Only set FORGE_LCDL_CDP_BRING_TO_FRONT=1 when you explicitly want Edge brought to the foreground. See EDGE-CDP-ATTACH.md.

Debug a single step:

FORGE_LCDL_PLAYWRIGHT_LIVE=1 python3 scripts/pa-build-outlook-archive-flow.py --block 05

Probe-only status (no writes):

FORGE_LCDL_PLAYWRIGHT_LIVE=1 python3 scripts/run-power-automate-guided-build.py --step 02_outlook_trigger

1. Trigger (manual reference)

  • Office 365 Outlook → When a new email arrives (V3)
  • Folder: Inbox
  • Include attachment content: Yes
  • Only with attachments: No

2. Initialize variables

Name Expression
receivedDate triggerOutputs()?['body/receivedDateTime']
year formatDateTime(variables('receivedDate'), 'yyyy')
month formatDateTime(variables('receivedDate'), 'MM')
day formatDateTime(variables('receivedDate'), 'dd')
stamp formatDateTime(variables('receivedDate'), 'yyyyMMdd-HHmmss')
safeId replace(replace(triggerOutputs()?['body/id'], '/', '_'), '+', '-')
folderPath concat('outlook/', variables('year'), '/', variables('month'), '/', variables('day'))
fileName concat(variables('stamp'), '-', variables('safeId'), '.eml')

Path helpers in Python mirror these rules: forge_lcdl.power_automate.archive_paths.

Before export:

  1. Excel Online (Business) → List rows present in a table on outlook/messages.xlsx, table Messages.
  2. Filter array where InternetMessageId equals triggerOutputs()?['body/internetMessageId'].
  3. Condition: if count > 0, Terminate as Succeeded (skip).

4. Export and save .eml

  1. Office 365 Outlook → Export email (V2) — Message Id: triggerOutputs()?['body/id']
  2. SharePoint → Create file
  3. Site: EnterpriseDelivery-Leo
  4. Folder Path: Shared Documents/Leo/Agentic/@{variables('folderPath')}
  5. File Name: @{variables('fileName')}
  6. File Content: body('Export_email_(V2)') — Export email (V2) returns the .eml payload as a string (do not use ?['body'])

5. Append index row

Excel Online (Business) → Add a row into a table

Workbook binding (automated in block 06 — do not use bare EnterpriseDelivery-Leo for Location):

Field Value
Location Chevron → type Enterprise Delivery-LeoSharePoint Site - Enterprise Delivery-Leo
Document Library Documents (designer label; maps to Shared Documents)
File Expression 'Leo/Agentic/outlook/messages.xlsx'
Table Messages (custom value OK if dropdown empty)
Row json(concat(...)) over index columns (see build_excel_row_json_expression in forge-lcdl)

Code view should show source as sites/softserveinc.sharepoint.com,{site-guid},{web-guid} and a non-empty item.

Column Value (example)
MessageId triggerOutputs()?['body/id']
InternetMessageId triggerOutputs()?['body/internetMessageId']
ConversationId triggerOutputs()?['body/conversationId']
ReceivedDateTime triggerOutputs()?['body/receivedDateTime']
From triggerOutputs()?['body/from']
To triggerOutputs()?['body/toRecipients']
Cc triggerOutputs()?['body/ccRecipients']
Bcc triggerOutputs()?['body/bccRecipients']
Subject triggerOutputs()?['body/subject']
HasAttachments triggerOutputs()?['body/hasAttachments']
BodyPreview triggerOutputs()?['body/bodyPreview']
SharePointFileLink outputs('Create_file')?['body/Path'] or Link
ArchivedAt utcNow()

Turn the flow On.

Verify with LCDL (read-only)

Edge must expose CDP with an M365-signed-in session (see EDGE-CDP-ATTACH.md).

cd forge-lcdl
pip install -e '.[playwright,files]'
FORGE_LCDL_PLAYWRIGHT_LIVE=1 python3 scripts/run-outlook-archive-verify-harness.py \
  --sharepoint-url 'https://softserveinc.sharepoint.com/sites/EnterpriseDelivery-Leo/Shared%20Documents/Forms/AllItems.aspx?id=%2Fsites%2FEnterpriseDelivery%2DLeo%2FShared%20Documents%2FLeo%2FAgentic&viewid=cc1d37d3%2D73c6%2D49f2%2Db36d%2D32971cc18ef8'

Optional: --received-date 2026-06-28T14:00:00Z, --message-id …, --internet-message-id ….

Success: JSON with "ok": true, non-zero eml_count, and matched_row when an id filter is passed.

Re-testing and idempotency

Symptom: Test → AutomaticallyWith a recently used trigger fails on Create file with 400 BadRequestA file with the name … already exists.

Cause: The archive filename is deterministic from message id + received time. Re-running the same trigger tries to create the same .eml again.

LCDL remediation (automated):

FORGE_LCDL_PLAYWRIGHT_LIVE=1 python3 scripts/pa-test-outlook-archive-remediation.py --json

This opens the flow designer, applies known fixes (SharePoint File Content expression, Excel Row json(concat(...)), Allow chunking off on Create file), saves, then tests with the most recent trigger — up to three rounds.

SharePoint overwrite: Block 05 and the remediation script turn off Settings → Allow chunking on Create file. With chunking disabled, Power Automate overwrites an existing file instead of failing.

Excel duplicate rows: Re-testing after a successful run can still Add a row twice for the same MessageId. Block 06 now builds this pipeline automatically:

  1. List rows present in a table — filter MessageId eq '@{triggerOutputs()?['body/id']}'
  2. Conditionempty(body('List_rows_present_in_a_table')?['value'])
  3. True branch: Compose (JSON string) → Parse JSONAdd a row with Row = body('Parse_JSON')

If List rows still shows File is required, open that step → Show all → set File to Leo/Agentic/outlook/messages.xlsx (expression or Browse), Table = Messages, then save.

Until List rows is valid, treat harness message-id match as the source of truth.

End-to-end test checklist

  1. Upload bootstrap messages.xlsx if missing.
  2. Create and enable the flow above.
  3. Send a test email to your Inbox (subject e.g. LCDL-PA archive test).
  4. Wait 1–3 minutes; check Run history in Power Automate.
  5. Refresh SharePoint Agentic library — confirm outlook/YYYY/MM/DD/*.eml and new row in messages.xlsx.
  6. Run the LCDL harness; expect "ok": true.

Test evidence

Record flow run id, test message InternetMessageId, and harness JSON output here after each validation run.

Date Flow run id Message id Harness ok
2026-06-28 (pending — build flow in Power Automate UI) Bootstrap upload + index read verified (row_count: 0 on empty index)

Phase 2 (optional)

  • Backfill flow: manual trigger + Graph GET /me/mailFolders/Inbox/messages with @odata.nextLink pagination (see Microsoft Graph mail folder messages API).
  • Child flow with HTTP trigger; invoke from Python via forge_lcdl.power_automate.http_trigger.invoke_http_flow.