Wicked Smart Data
LearnInsightsAboutContact
Sign InLet's Build
LearnInsightsAboutContact
Sign InLet's Build
Wicked Smart Data

Intelligence, automation, and expert execution — plus an elite library of free knowledge. We turn complexity into competitive advantage.

Start a conversation

Platform

  • Learning Paths
  • Insights
  • RSS Feed

Company

  • About
  • Contact
  • Work With Us

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Wicked Smart Data. All rights reserved.

Intelligence · Automation · Advantage

All Insights
Power Automate

Building a Multi-Step Microsoft 365 Connector Flow That Handles Attachments: Receiving Files via Outlook, Storing to SharePoint, and Notifying Approvers in Teams

Build a production-ready document intake pipeline in Power Automate that catches emailed attachments the moment they arrive, saves them to SharePoint with proper metadata, and routes context-rich approval notifications through Teams. This deep-dive lesson covers every edge case — inline image filtering, null-safe approval responses, error handling, and consolidated multi-attachment approvals.

🔥 Expert27 min readNov 5, 2026Updated Nov 5, 2026
Building a Multi-Step Microsoft 365 Connector Flow That Handles Attachments: Receiving Files via Outlook, Storing to SharePoint, and Notifying Approvers in Teams
On this page
  • Introduction
  • Prerequisites
  • Understanding the Architecture Before You Build
  • Step 1: Configuring the Outlook Trigger with Precision
  • Step 2: Validating Attachments with a Condition
  • Step 3: Initializing Variables for Cross-Loop Data
  • Step 4: Building the Apply to Each Loop for Attachments
  • 4a: Setting Document Category from Subject Line
  • 4b: Uploading the File to SharePoint
  • 4c: Updating SharePoint List Item Metadata
  • Step 5: Composing the Teams Notification with Full Context
  • Step 6: Starting the Approval and Waiting for Response
  • Step 7: Branching on Approval Outcome
  • Step 8: Adding Error Handling Around File Upload
  • Step 9: Post-Loop Cleanup and Audit Trail
  • Advanced Variation: Consolidated Approval for Multi-Attachment Emails
  • Hands-On Exercise
  • Common Mistakes & Troubleshooting
  • Mistake 1: Attachment Content Is Empty
  • Mistake 2: Flow Runs for Every Email, Not Just Emails with Attachments
  • Mistake 3: SharePoint Create File Fails with "Path Not Found"
  • Mistake 4: Approval Response Expressions Return Null
  • Mistake 5: Loop Sends Approval for Inline Images
  • Mistake 6: Teams Message Posts But Approval Is Never Received
  • Summary & Next Steps
  • Building a Multi-Step Microsoft 365 Connector Flow That Handles Attachments: Receiving Files via Outlook, Storing to SharePoint, and Notifying Approvers in Teams

    Introduction

    Every organization has a version of this problem. Someone emails a contract, an invoice, a signed form, or a vendor proposal to a shared inbox — and then it disappears. It lands in someone's email, maybe gets forwarded, maybe gets saved to a desktop folder, maybe doesn't get saved at all. The approver who needs to review it doesn't know it arrived. The person who sent it has no idea whether anyone looked at it. Three weeks later someone asks "whatever happened to that NDA we sent over?" and the answer is a collective shrug.

    The automation you'll build in this lesson solves that entire problem in a single flow. You're going to wire together three Microsoft 365 connectors — Outlook, SharePoint, and Teams — into a coherent pipeline that catches incoming email attachments the moment they arrive, saves them to a structured SharePoint document library with proper metadata, and fires a contextual approval notification to the right people in Teams. By the time you finish, you'll have a production-ready pattern that you can adapt for contracts, invoices, HR documents, vendor submissions, or anything else your organization receives via email.

    What you'll learn:

    • How to trigger a flow on incoming Outlook email and correctly extract attachment content (not just attachment metadata)
    • How to handle the Apply to Each loop that the Outlook connector forces on attachments, and why collapsing that loop carelessly creates subtle bugs
    • How to upload file content to SharePoint and set custom metadata columns in a single choreographed sequence
    • How to compose a rich, context-aware Teams notification that links directly to the SharePoint file, not just announces that something arrived
    • How to route the approval response — approved or rejected — back to the original sender with a meaningful message rather than leaving them in the dark

    Prerequisites

    You should be comfortable with the Power Automate designer and have already built at least one trigger-based flow. You need to understand what Power Automate triggers do conceptually, because this lesson won't re-explain trigger fundamentals — it will show you how to push them into production patterns. You'll also need:

    • A Microsoft 365 work or school account with Power Automate access
    • An Outlook shared mailbox or personal mailbox you can test with (a dedicated test mailbox is strongly recommended)
    • A SharePoint site where you have Contribute or higher permissions, with a document library that has at least two custom metadata columns: Sender Email (single line of text) and Document Category (choice field)
    • A Teams channel where approval notifications will be posted
    • Familiarity with working with conditions, loops, and variables in Power Automate, particularly Apply to Each loops

    Understanding the Architecture Before You Build

    Before touching the designer, map out the data flow. This is advice that separates people who build fragile flows from people who build durable ones: spend fifteen minutes thinking about what data lives where and how it moves between steps.

    Here's the shape of what you're building:

    1. Trigger: A new email arrives in a specific mailbox with an attachment
    2. Filter: Confirm the email actually has attachments (the trigger filter alone isn't always sufficient)
    3. Loop: Iterate over each attachment (Outlook returns them as an array)
    4. For each attachment:
      • Get the full attachment content (the trigger only gives you metadata by default in some configurations)
      • Upload the file to a specific SharePoint folder
      • Update the SharePoint list item with metadata (sender, date, category derived from subject line keywords)
      • Compose and send a Teams adaptive card to the approver channel
      • Wait for the approval response
      • Branch on Approved vs. Rejected
      • Reply to the original sender with the outcome

    The critical architectural decision here is handling the Apply to Each loop correctly. When an email arrives with three attachments, your flow will run the entire inner sequence three times — once per attachment. That means three SharePoint uploads, three Teams notifications, and three approval waits. For most real-world document intake scenarios, that's exactly right behavior. But you need to be conscious of it, especially when the Teams notification and approval are inside the loop, because approvers will get three separate approval requests.

    An alternative architecture — processing all attachments first, then sending one consolidated approval — requires composing an array of SharePoint URLs and passing that into a single notification. We'll cover that pattern in the advanced variations section. Start with the per-attachment pattern because it's cleaner to debug and handles the most common case where each attachment is a distinct document.

    Note

    The Outlook connector's "When a new email arrives" trigger has two variants: one for personal mailboxes and one for shared mailboxes. The shared mailbox version requires that the mailbox be an actual Exchange shared mailbox (not just a distribution list) and that the connection account have "Full Access" permission on that mailbox. If you're setting up a document intake process, use a dedicated shared mailbox like documents@yourcompany.com rather than someone's personal inbox.


    Step 1: Configuring the Outlook Trigger with Precision

    Create a new Automated cloud flow. When asked to choose a trigger, search for "When a new email arrives" and select the one that matches your mailbox type. For this lesson we'll use the Office 365 Outlook — When a new email arrives (V3) trigger, which is the current version for personal mailboxes and the most fully-featured.

    In the trigger configuration, expand Show advanced options immediately. These settings determine whether your flow even fires for the right emails, and skipping them means your flow either runs on everything or misses things it should catch.

    Key trigger settings to configure:

    • Folder: Set this to Inbox or, better yet, a dedicated subfolder like Inbox/DocumentSubmissions. Routing to a subfolder using Outlook rules keeps the flow from firing on every email, which matters for licensing and noise.
    • Include Attachments: Set to Yes. This is not optional — if you leave it at No, the attachment content will not be available in the trigger outputs, and you'll spend an hour wondering why your file content is empty.
    • Only with Attachments: Set to Yes. This filters at the trigger level so the flow doesn't even start for emails without attachments.
    • Subject Filter: Consider adding a keyword like [SUBMIT] that senders include in the subject line. This is a lightweight but effective way to distinguish intentional submissions from people who accidentally email the inbox.

    Warning

    Setting "Only with Attachments" to Yes at the trigger level does not guarantee that the Attachments array in the trigger body will always have items — edge cases exist with inline images embedded in HTML emails, which Outlook sometimes treats as attachments. Always add an explicit condition downstream to verify the attachment array is not empty.

    After saving the trigger, use a real email to test it. Send an email with one PDF attachment to the mailbox. Click Test in the designer (top right), select With a recently triggered event, and pick the run. This gives you the actual trigger output to examine — do that before building the rest of the flow. Look at the JSON structure of the trigger body. You'll see fields like from, subject, body, receivedDateTime, and attachments as an array. Each attachment object has name, contentType, and contentBytes (the base64-encoded file content).


    Step 2: Validating Attachments with a Condition

    Add a Condition action immediately after the trigger. This is your safety net against the inline-image false positive problem described above.

    The expression to check whether the attachments array has real items is:

    greater(length(triggerOutputs()?['body/attachments']), 0)
    

    Write this in the expression editor of the left-hand condition field, and set it to is equal to true.

    In the If No branch, add a Terminate action with status Succeeded and a message like No valid attachments found — flow terminated cleanly. This is better than leaving the No branch empty because it makes the run history readable. When you look at run history six weeks later, a clean termination with a message tells you what happened; an empty branch tells you nothing.

    Tip

    Some teams add a secondary condition inside the attachment loop to filter by file type — for example, only processing PDFs and Word documents while skipping .jpg or .png files that are likely inline images. The expression contains(items('Apply_to_each')?['contentType'], 'pdf') checks for PDFs. Build the basic flow first, then layer in this refinement once you understand your real-world email traffic.

    All subsequent actions go into the If Yes branch.


    Step 3: Initializing Variables for Cross-Loop Data

    Before the Apply to Each loop, initialize any variables you'll need inside the loop. Power Automate requires variables to be initialized before the scope where they're used — you can't initialize a variable inside an Apply to Each.

    For this flow, initialize two variables:

    Variable 1: SharePoint File URL

    • Action: Initialize variable
    • Name: varSharePointFileURL
    • Type: String
    • Value: (empty)

    Variable 2: Document Category

    • Action: Initialize variable
    • Name: varDocumentCategory
    • Type: String
    • Value: General

    The Document Category variable is going to hold a value derived from the email subject line using a keyword match. You'll set it inside the loop using a switch or condition. Initializing it with General as a default means you always have a fallback value even if none of the keyword conditions match.


    Step 4: Building the Apply to Each Loop for Attachments

    Add an Apply to each action. For the input, click into the field and select the dynamic content value Attachments from the trigger. Power Automate will warn you that this creates a loop — that's exactly what you want.

    Inside the loop, you're going to build the following sequence:

    1. Set Document Category from subject keywords
    2. Upload file to SharePoint
    3. Update SharePoint list item metadata
    4. Compose the Teams notification
    5. Wait for approval response
    6. Branch and reply to sender

    Let's work through each one carefully.

    4a: Setting Document Category from Subject Line

    Add a Switch action inside the loop. For the On value, use this expression:

    toLower(triggerOutputs()?['body/subject'])
    

    Converting to lowercase makes your keyword matching case-insensitive without having to write multiple cases.

    Add three cases:

    • Case 1 — Contains "invoice": You can't use contains() directly in a Switch On value, so handle this differently. Actually, the better pattern here is to use nested Condition actions instead of Switch, since Switch does exact matching and you need substring matching.

    Replace the Switch with a Condition chain. Add a Condition action with this expression:

    contains(toLower(triggerOutputs()?['body/subject']), 'invoice')
    

    If true: Set variable varDocumentCategory to Invoice

    In the No branch, add another Condition:

    contains(toLower(triggerOutputs()?['body/subject']), 'contract')
    

    If true: Set variable varDocumentCategory to Contract

    If No: Add one more Condition for nda or agreement:

    or(contains(toLower(triggerOutputs()?['body/subject']), 'nda'), contains(toLower(triggerOutputs()?['body/subject']), 'agreement'))
    

    If true: Set to NDA/Agreement. If No: leave varDocumentCategory at its initialized value of General.

    This nested structure correctly chains your keyword detection without requiring exact matches.

    Key insight

    This keyword extraction pattern is functional but brittle — it breaks the moment someone sends an invoice without the word "invoice" in the subject. For a production flow that needs reliable categorization, replace this logic with an AI Builder text classification model, or parse the email body for structured keywords. The AI Builder integration article covers exactly this pattern. For the lesson's purposes, keyword matching demonstrates the concept without introducing AI dependencies.

    4b: Uploading the File to SharePoint

    Add a Create file action from the SharePoint connector. This action takes file content and creates a physical file in a document library. Configure it as follows:

    • Site Address: Select your SharePoint site from the dropdown (don't type a URL — use the picker, as the internal site ID matters)
    • Folder Path: /Shared Documents/DocumentIntake (or whatever your library and folder path is — note this is the library's URL-relative path, not the display name)
    • File Name: Use the dynamic content Attachment Name from inside the Apply to Each loop. This is items('Apply_to_each')?['name'] in expression form
    • File Content: Use Attachment Content — this is items('Apply_to_each')?['contentBytes']. This is the base64-encoded file content. The SharePoint connector handles the base64 decoding automatically; do not add a base64ToString() wrapper here

    After running successfully, the Create file action returns a set of output properties including {Id} (the list item ID), ItemId (also the list item ID, as an integer), Path, and WebUrl. You'll use ItemId in the next step and WebUrl in the Teams notification.

    Warning

    SharePoint document library folder paths are case-sensitive and must use forward slashes. If you type /shared documents/documentintake when the actual library name is Shared Documents, the action will fail with a path error. Navigate to your library in SharePoint and copy the URL path from the browser — then strip the site URL prefix to get the relative path.

    4c: Updating SharePoint List Item Metadata

    The Create file action uploads the file but doesn't set custom metadata columns. For that, add an Update file properties action from the SharePoint connector immediately after Create file.

    • Site Address: Same site as before
    • Library Name: Select your document library
    • Id: Use the dynamic content ItemId from the Create file action output
    • Sender Email: Use From from the trigger dynamic content — this is the sender's email address
    • Document Category: Use the variable varDocumentCategory (the dynamic content pill, not a hardcoded value)

    This two-step pattern — Create file, then Update file properties — is the correct SharePoint attachment upload sequence. Many people try to set metadata inside the Create file action and discover the custom columns don't appear as options there. The Create file action only knows about the file content, not the library's metadata schema. Update file properties is what lets you write to those custom columns.

    After Update file properties, store the SharePoint file URL in your variable:

    Add a Set variable action:

    • Name: varSharePointFileURL
    • Value: Use the dynamic content Link to item or {WebUrl} from the Create file outputs

    Step 5: Composing the Teams Notification with Full Context

    This is where many flows fall short — they send a bare-minimum Teams message that says something like "A file was received" with no actionable context. Your approvers should be able to understand the entire submission from the Teams notification alone, without having to click through to SharePoint first.

    Before building the Teams message, add a Compose action to build the formatted message body. Using Compose for the message text rather than typing directly into the Teams action makes the message much easier to edit later without touching the action itself.

    Add a Compose action and build an expression like this:

    concat(
      '**New Document Submission**',
      '\n\n',
      '**From:** ', triggerOutputs()?['body/from'],
      '\n',
      '**Received:** ', formatDateTime(triggerOutputs()?['body/receivedDateTime'], 'dddd, MMMM d yyyy h:mm tt'),
      '\n',
      '**Subject:** ', triggerOutputs()?['body/subject'],
      '\n',
      '**File:** ', items('Apply_to_each')?['name'],
      '\n',
      '**Category:** ', variables('varDocumentCategory'),
      '\n\n',
      '**[View in SharePoint](', variables('varSharePointFileURL'), ')**'
    )
    

    This produces a well-structured message with a direct link to the file. The approver sees who sent it, when it arrived, what the subject line was, what the file is called, what category it was classified as, and a clickable link to the actual document in SharePoint — all before even clicking anything.

    Now add the Post a message in a chat or channel action from Teams connector:

    • Post as: Flow bot
    • Post in: Channel
    • Team: Select your team
    • Channel: Select your approver notification channel
    • Message: Use the output of the Compose action

    Tip

    For higher-stakes approval workflows, consider replacing the basic Teams message with an Adaptive Card that includes inline Approve/Reject buttons. That pattern — where approvers can respond directly in Teams without navigating to Power Automate's approval center — is covered in depth in the adaptive card approvals article. The basic Teams message is sufficient for the pattern we're building here, and you can upgrade it once the pipeline is working end-to-end.


    Step 6: Starting the Approval and Waiting for Response

    Add a Start and wait for an approval action from the Approvals connector. This action is synchronous within the flow — the flow execution pauses here and resumes only when an approver responds. This is a critical behavior to understand: a Power Automate run can pause for days or weeks waiting for an approval. For most document intake scenarios, that's correct. If you need a timeout/escalation pattern, the approval escalation article covers exactly that.

    Configure the approval action:

    • Approval type: Approve/Reject — First to respond (for single approver or any-one-of-a-group scenarios) or Everyone must approve (for sequential sign-off)
    • Title: Use an expression like:
      concat('Document Review: ', items('Apply_to_each')?['name'], ' from ', triggerOutputs()?['body/from'])
      
    • Assigned to: The email address of your approver (or a semicolon-separated list for multiple approvers)
    • Details: Paste the same Compose output you used for the Teams message, plus any additional context. Including the SharePoint URL here is important because approvers often respond from the Power Automate approval center, not Teams.
    • Item link: Paste varSharePointFileURL — this makes the approval notification in the Approvals center clickable directly to the SharePoint file
    • Item link description: View document in SharePoint

    After the Start and wait for an approval action completes, you have access to the approval outcome through the Outcome property (which will be Approve or Reject) and the Response summary which includes comments from the approver.


    Step 7: Branching on Approval Outcome

    Add a Condition action after the approval step. Set the condition:

    outputs('Start_and_wait_for_an_approval')?['body/outcome']
    

    Is equal to: Approve

    In the If Yes (Approved) branch:

    1. Update file properties on the SharePoint item to set an Approval Status choice column to Approved
    2. Send an email (V2) back to the original sender

    For the approval confirmation email:

    • To: triggerOutputs()?['body/from']
    • Subject: concat('Approved: ', items('Apply_to_each')?['name'])
    • Body: Build a message that tells them the document was approved, by whom, when, and any comments the approver left:
    concat(
      'Your document "', items('Apply_to_each')?['name'], '" has been approved.\n\n',
      'Approved by: ', outputs('Start_and_wait_for_an_approval')?['body/responses'][0]?['responder/email'], '\n',
      'Response time: ', formatDateTime(outputs('Start_and_wait_for_an_approval')?['body/responses'][0]?['requestDate'], 'dddd MMMM d, yyyy'), '\n\n',
      'Comments: ', if(empty(outputs('Start_and_wait_for_an_approval')?['body/responses'][0]?['comments']), 'No comments provided.', outputs('Start_and_wait_for_an_approval')?['body/responses'][0]?['comments']),
      '\n\nThe document has been stored in SharePoint: ', variables('varSharePointFileURL')
    )
    

    In the If No (Rejected) branch:

    1. Update file properties to set Approval Status to Rejected
    2. Optionally: Delete file from SharePoint if your policy is to remove rejected documents (use the File Identifier from the Create file action)
    3. Send an email to the sender explaining the rejection, including the approver's comments

    Key insight

    Sending the SharePoint URL to the sender even on approval is a meaningful detail. It transforms the sender's mental model from "I sent a file by email" to "I have a document that now lives in SharePoint." Over time, this trains senders to expect that submitted documents go into a managed system — which is exactly the culture shift document intake automation is meant to create.


    Step 8: Adding Error Handling Around File Upload

    Production flows fail. Networks hiccup, SharePoint throttles, the attachment content is malformed. Without error handling, your flow dies silently and nobody knows the document wasn't processed. This is not acceptable for a document intake pipeline.

    Wrap the Create file action in a scope, and configure the scope's Run after settings for the downstream error notification. Here's the minimal pattern:

    After the Create file action (but still inside the Apply to Each loop), add a Condition that checks whether the Create file action succeeded:

    equals(actions('Create_file')?['status'], 'Succeeded')
    

    If it failed, send an alert email to a designated admin address and to the original sender explaining that their document could not be processed and they should resubmit. Include the error message:

    actions('Create_file')?['error']?['message']
    

    For more sophisticated error handling patterns including retry logic and dead-letter queuing, the error handling article is worth studying after you get this flow working.


    Step 9: Post-Loop Cleanup and Audit Trail

    After the Apply to Each loop completes (all attachments have been processed), add a final step outside the loop: post a summary message to a separate audit log channel in Teams or update a SharePoint list that tracks email submissions.

    Add a Create item action on a SharePoint list called DocumentIntakeLog with columns:

    • Title: Subject line of the email
    • Sender: Email from address
    • ReceivedAt: Timestamp from trigger
    • AttachmentCount: Use length(triggerOutputs()?['body/attachments']) to get the count
    • ProcessedAt: utcNow()

    This audit log is what lets you answer "how many documents came in last month?" or "has the Jones contract been submitted yet?" without digging through run history. It also provides a lightweight compliance record that the submission was received and processed.


    Advanced Variation: Consolidated Approval for Multi-Attachment Emails

    The per-attachment loop pattern described above works well when each attachment is a distinct document requiring its own approval. But sometimes an email arrives with five attachments that together constitute a single submission — a contract package, for instance, with the main agreement, exhibits, and a cover sheet. Sending five separate approval requests for that is incorrect behavior.

    Here's the alternative architecture:

    1. Run a first Apply to Each loop that only uploads files to SharePoint and captures the resulting URLs into a string array variable using the union() function pattern or an append-to-array variable
    2. After the loop, compose a single consolidated Teams message that lists all uploaded files with their SharePoint links
    3. Send one approval request referencing all files
    4. On the approval outcome, loop back through the SharePoint items and update all their Approval Status columns in a second Apply to Each

    The key to this pattern is the append to array variable action. Initialize a variable of type Array before the first loop:

    varSharePointURLs = []
    

    Inside the upload loop, after each Create file action, append to the array:

    union(variables('varSharePointURLs'), array(outputs('Create_file')?['body/{WebUrl}']))
    

    Then after the loop, use join(variables('varSharePointURLs'), '\n') to format the URL list into the consolidated notification.

    This is a more complex flow to build and debug, but it's the correct architecture for multi-document submission packages. See the chaining Microsoft 365 connectors article for more patterns on passing data cleanly between multiple connector actions in a flow.


    Hands-On Exercise

    Build this flow from scratch in your own environment using the following scenario:

    Scenario: Your team receives vendor proposals via email to proposals@yourcompany.com. Each proposal arrives as a PDF attached to an email with "Proposal" in the subject line. When a proposal arrives, it should be:

    1. Saved to Shared Documents/VendorProposals/[Year] in your SharePoint site
    2. The SharePoint item metadata should capture the sender's email and the proposal date (derived from the email received date, formatted as YYYY-MM-DD)
    3. A Teams message in the #procurement-reviews channel should notify the procurement team with the sender name, subject, and a direct SharePoint link
    4. An approval should be sent to your procurement manager
    5. On approval, the sender should receive a confirmation email stating their proposal is under review (yes, approval here means "accepted for review," not necessarily accepted)
    6. On rejection (spam/irrelevant), the file should be deleted from SharePoint and the sender notified

    Specific challenges to work through:

    1. How do you create the year-based folder path dynamically? Use formatDateTime(triggerOutputs()?['body/receivedDateTime'], 'yyyy') to get the year and concatenate it into the folder path string. Test what happens when the folder doesn't yet exist — SharePoint's Create file action will create missing folders automatically, but only one level deep.

    2. How do you extract just the sender's display name (not their email address) for the Teams message? The from field returns a formatted string like "John Smith" <john.smith@vendor.com>. Use the expression first(split(triggerOutputs()?['body/from'], '<')) to extract the display name portion, then trim whitespace and quotes.

    3. Add a 48-hour reminder: if the approval hasn't been responded to after 48 hours, send a reminder Teams message to the approver. This requires the approval escalation pattern using a parallel branch with a Delay action — implement it.


    Common Mistakes & Troubleshooting

    Mistake 1: Attachment Content Is Empty

    Symptom: The SharePoint file is created but is 0 bytes. When you open it, it's blank.

    Cause: You either didn't set "Include Attachments" to Yes on the trigger, or you're referencing the attachment content from the wrong output.

    Fix: Open the trigger settings and confirm Include Attachments is Yes. Inside the Apply to Each loop, make sure your File Content field references Attachment Content (the dynamic content item from within the loop), not anything from outside the loop. If you used an expression, verify it's items('Apply_to_each')?['contentBytes'] and not triggerOutputs()?['body/attachments'][0]?['contentBytes'] (the latter only ever gets the first attachment).

    Mistake 2: Flow Runs for Every Email, Not Just Emails with Attachments

    Symptom: Flow run history shows hundreds of runs, most of which terminate on the attachment condition.

    Cause: The trigger's "Only with Attachments" filter was left at No.

    Fix: Edit the trigger, expand advanced options, set "Only with Attachments" to Yes. Note that changing trigger settings requires all existing runs to complete before the new settings take effect. You may also want to implement a subject line filter at the trigger level to further reduce noise.

    Mistake 3: SharePoint Create File Fails with "Path Not Found"

    Symptom: Create file action throws an error: The specified path '/Shared Documents/NonExistent' does not exist.

    Cause: You typed a folder path that doesn't exist and contains more than one level of non-existent folders. Create file creates missing folders only one level deep.

    Fix: Pre-create the folder structure in SharePoint, or add a Create new folder action before the Create file action to ensure the target folder exists. For the year-based folder pattern in the exercise, add a Create new folder step each time the flow runs — SharePoint will return an error if the folder already exists, so wrap it in a scope and configure its failure as acceptable using the error handling techniques covered in the linked lesson.

    Mistake 4: Approval Response Expressions Return Null

    Symptom: The email sent to the original sender after approval shows blank values where the approver name and comments should be.

    Cause: The responses array from the approval action is empty or the expression path is wrong. outputs('Start_and_wait_for_an_approval')?['body/responses'][0]?['responder/email'] only works if at least one response exists.

    Fix: Always guard approval response expressions with the if(empty(...), 'fallback value', ...) pattern. The approval action's status being Approved or Rejected doesn't guarantee the responses array has the structure you expect — in edge cases like automatic rejection on approval expiry, the array may be empty.

    Mistake 5: Loop Sends Approval for Inline Images

    Symptom: Approvers receive approval requests for tiny .png or .gif files that are email signatures or inline images, not actual documents.

    Cause: HTML emails embed images as attachments. The trigger with "Only with Attachments" can't distinguish between meaningful attachments and inline image attachments.

    Fix: Add a condition at the top of your Apply to Each loop:

    and(
      not(equals(items('Apply_to_each')?['contentType'], 'image/png')),
      not(equals(items('Apply_to_each')?['contentType'], 'image/gif')),
      not(equals(items('Apply_to_each')?['contentType'], 'image/jpeg')),
      greater(items('Apply_to_each')?['contentBytes'] | length, 1024)
    )
    

    The size check (greater than 1024 bytes) is an additional filter that eliminates the tiny tracking pixels that sometimes show up as attachments. If this condition is false, skip to the next iteration — in Power Automate you can't continue a loop directly, but you can wrap the entire loop body in an if-yes branch of this condition and leave the if-no branch empty.

    Warning

    Filtering by content type alone is not foolproof. Some vendors send important documents as JPEG scans. If your use case involves scanned documents, skip the image content-type filter and rely only on a minimum file size threshold (e.g., greater than 50KB).

    Mistake 6: Teams Message Posts But Approval Is Never Received

    Symptom: The Teams channel shows the notification message, but the approver never gets an approval request email and nothing shows in their Power Automate Approvals center.

    Cause: The "Assigned to" field in the Start and wait for an approval action contains an email address that doesn't match an active Microsoft 365 user, or it contains a distribution list alias rather than a user principal name.

    Fix: Approval requests must go to individual M365 users (or M365 group email aliases in some configurations, but this is unreliable). Use the person's actual UPN (usually their email address). Test by hardcoding your own email first to confirm the approval request arrives, then switch to the approver's email.


    Summary & Next Steps

    You've built a document intake pipeline that spans three Microsoft 365 services and handles the full lifecycle of an emailed attachment: receipt, storage, approval, and confirmation. The specific patterns you should now feel confident applying elsewhere include:

    • Trigger-level filtering (attachments, subject keywords, specific folder) to reduce noise before the flow even starts
    • The two-step SharePoint write pattern: Create file for content, then Update file properties for metadata
    • Variable initialization before loops so data persists across iterations and is accessible after the loop completes
    • Defensive approval response handling with null guards on the responses array
    • Inline image filtering inside the attachment loop so your approvers don't get swamped with 1-pixel tracking pixel approvals
    • Post-loop audit logging to SharePoint for a queryable record of all submissions

    The flow you've built today handles the common case cleanly. Here's where to go next depending on your real-world needs:

    If your approval process has multiple stages — for example, a manager approves first, then finance approves — read the multi-stage approval chains article to understand how to chain sequential and parallel approvals without losing the SharePoint context between stages.

    If you need routing logic — different approvers depending on the document type, the sender's department, or the attachment's size — the dynamic approval routing article gives you the conditional routing pattern that slots directly into this flow.

    If you want to extract data from the attachments themselves — parsing invoice numbers from PDFs, reading table data from Excel files — that's a step up in complexity covered in automating data collection from email attachments. Once you've combined the intake pipeline with data extraction, you're building something that genuinely replaces a category of manual data entry work.

    If your flow starts misbehaving in production, the most useful debugging tool is run history combined with Flow Checker. The run history and Flow Checker article covers how to read the execution timeline, inspect input/output at each action, and identify whether a failure is a logic error, an expression error, or a connector error — three very different things with very different fixes.

    The pattern you've built isn't just a document intake flow. It's a template for any process that involves receiving external inputs by email, persisting them to a managed system, routing them for human review, and closing the loop with the submitter. Swap out the specifics and you've got the architecture for HR onboarding documents, client deliverable submissions, compliance certifications, vendor invoices, and dozens of other high-value automation targets.

    Work With Us

    From insight to implementation

    Reading is the start. When you're ready to build the data, automation, or AI systems behind it, our team turns strategy into shipped results.

    Let's Build

    Flow Automation Basics

    Previous

    Automating Recurring Calendar-Based Reminders with Power Automate: Building Scheduled Flows That Check SharePoint Dates, Calculate Deadlines, and Send Personalized Outlook Notifications

    Related Insights

    Power AutomatePractitioner

    Implementing Power Automate Managed Identity Authentication for Azure Connector Actions: Eliminating Stored Credentials in Production Cloud Flows

    23 min
    Power AutomatePractitioner

    Automating Web Application Login and Session Persistence in Power Automate Desktop: Handling Cookie Consent, SSO Redirects, and Session Timeouts in Unattended Browser Flows

    25 min
    Power AutomatePractitioner

    Automating Recurring Calendar-Based Reminders with Power Automate: Building Scheduled Flows That Check SharePoint Dates, Calculate Deadlines, and Send Personalized Outlook Notifications

    22 min

    On this page

    • Introduction
    • Prerequisites
    • Understanding the Architecture Before You Build
    • Step 1: Configuring the Outlook Trigger with Precision
    • Step 2: Validating Attachments with a Condition
    • Step 3: Initializing Variables for Cross-Loop Data
    • Step 4: Building the Apply to Each Loop for Attachments
    • 4a: Setting Document Category from Subject Line
    • 4b: Uploading the File to SharePoint
    • 4c: Updating SharePoint List Item Metadata
    • Step 5: Composing the Teams Notification with Full Context
    • Step 6: Starting the Approval and Waiting for Response
    • Step 7: Branching on Approval Outcome
    • Step 8: Adding Error Handling Around File Upload
    • Step 9: Post-Loop Cleanup and Audit Trail
    • Advanced Variation: Consolidated Approval for Multi-Attachment Emails
    • Hands-On Exercise
    • Common Mistakes & Troubleshooting
    • Mistake 1: Attachment Content Is Empty
    • Mistake 2: Flow Runs for Every Email, Not Just Emails with Attachments
    • Mistake 3: SharePoint Create File Fails with "Path Not Found"
    • Mistake 4: Approval Response Expressions Return Null
    • Mistake 5: Loop Sends Approval for Inline Images
    • Mistake 6: Teams Message Posts But Approval Is Never Received
    • Summary & Next Steps