Handbook
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 tableMessages)
LCDL supplies conventions and read-only verification; Power Automate performs writes.
Prerequisites
- Access to
https://softserveinc.sharepoint.com/sites/EnterpriseDelivery-Leoand folder Shared Documents/Leo/Agentic. - Power Automate environment (e.g.
Default-6c51c659-9d52-41af-81f7-dde16380e813). - 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.
3. Duplicate guard (recommended)
Before export:
- Excel Online (Business) → List rows present in a table on
outlook/messages.xlsx, tableMessages. - Filter array where
InternetMessageIdequalstriggerOutputs()?['body/internetMessageId']. - Condition: if count > 0, Terminate as Succeeded (skip).
4. Export and save .eml
- Office 365 Outlook → Export email (V2) — Message Id:
triggerOutputs()?['body/id'] - SharePoint → Create file
- Site: EnterpriseDelivery-Leo
- Folder Path:
Shared Documents/Leo/Agentic/@{variables('folderPath')} - File Name:
@{variables('fileName')} - File Content:
body('Export_email_(V2)')— Export email (V2) returns the.emlpayload 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-Leo → SharePoint 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 → Automatically → With a recently used trigger fails on Create file with 400 BadRequest — A 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:
- List rows present in a table — filter
MessageId eq '@{triggerOutputs()?['body/id']}' - Condition —
empty(body('List_rows_present_in_a_table')?['value']) - True branch: Compose (JSON string) → Parse JSON → Add 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
- Upload bootstrap
messages.xlsxif missing. - Create and enable the flow above.
- Send a test email to your Inbox (subject e.g.
LCDL-PA archive test). - Wait 1–3 minutes; check Run history in Power Automate.
- Refresh SharePoint Agentic library — confirm
outlook/YYYY/MM/DD/*.emland new row inmessages.xlsx. - 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/messageswith@odata.nextLinkpagination (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.