Stop manually downloading attachments and copying data into spreadsheets. This hands-on lesson teaches you how to build a Power Automate flow that monitors your inbox, extracts CSV attachment content, and automatically writes parsed rows into an Excel table — no code required.

Every Monday morning, someone on your team opens their inbox, downloads a dozen sales reports from regional managers, copies the numbers out of each CSV or Excel file, and pastes them into a master spreadsheet. It takes two hours. It happens every week. Nobody enjoys it, and occasionally a file gets missed or a number gets transposed. This is exactly the kind of repetitive, error-prone work that Power Automate was built to eliminate.
Power Automate is Microsoft's cloud-based workflow automation tool. Think of it as a digital assistant that watches for specific things to happen — like an email arriving with an attachment — and then automatically does a sequence of tasks in response, without you lifting a finger. It connects to hundreds of services: Outlook, Gmail, SharePoint, Excel, Dataverse, Teams, and more. Once you build a flow (that's what Power Automate calls an automated workflow), it runs on its own, around the clock.
By the end of this lesson, you'll have built a complete working flow that monitors an email inbox, detects incoming attachments, extracts their content, and stores the data in a structured location. You'll understand not just the steps, but why each step is necessary — so you can adapt the pattern to your own real-world scenarios.
What you'll learn:
Before you start building, make sure you have access to the following:
You don't need any prior Power Automate or coding experience. We'll explain every concept as we go.
Before touching the tool, let's map out what we're actually building. Understanding the structure first will save you a lot of confusion later.
A Power Automate flow has three kinds of components:
Our flow will follow this sequence:
[Trigger] New email arrives in Outlook
→ [Condition] Does the email have attachments?
→ Yes: [Action] Get the attachment content
[Action] Parse the CSV data
[Action] Loop through each row
[Action] Add each row to an Excel table
→ No: [Action] Do nothing (or send an alert)
Think of this like a sorting machine at a post office. Mail arrives (trigger), the machine checks if the package meets certain criteria (condition), and if it does, it routes the package to the right bin and processes it (actions). If it doesn't meet criteria, it goes to a different bin or gets set aside.
One important concept to internalize now: everything in Power Automate is data passing between steps. The trigger produces data (email subject, sender, body, attachments). Each action consumes data from earlier steps and produces new data. Your job as the flow builder is to wire those data outputs and inputs together correctly.
One of the most common beginner mistakes is building the collection flow before the destination exists. Let's avoid that by creating the Excel table first.
Open OneDrive for Business (onedrive.com with your work account) and create a new folder called Sales Reports. Inside that folder, create a new Excel workbook and name it Consolidated_Sales.xlsx.
Open the workbook. In the first sheet (rename it Data for clarity by right-clicking the tab), create headers in row 1. For this lesson, we'll use a realistic sales scenario. Enter these column headers across columns A through F:
Region | Submitted By | Report Date | Product | Units Sold | Revenue
Now select those headers plus a few empty rows below them. On the Excel ribbon, click Insert → Table. Make sure "My table has headers" is checked, then click OK. Name the table SalesData by clicking on it and typing in the Table Name field on the Table Design tab.
Why does this matter? Power Automate's Excel connector works specifically with named tables, not with loose cell ranges. The table structure gives Power Automate a stable, predictable schema to write into. Without it, you'd have to manually calculate which row to write to every time, which is fragile and complicated.
Save and close the file.
Navigate to make.powerautomate.com and sign in. On the left sidebar, click My flows, then click New flow → Automated cloud flow.
Give your flow a descriptive name: "Email Attachment → Sales Data Collector". In the search box under "Choose your flow's trigger," type "email" and select "When a new email arrives (V3)" from the Outlook connector. Click Create.
You'll land in the flow designer — a canvas where you visually chain together triggers and actions.
Click on the trigger card to expand its settings. You'll see several configuration options. Here's how to set them up thoughtfully:
Tip: The "Only with Attachments" filter is a trigger-level filter, meaning the flow won't even start for emails without attachments. This is more efficient than starting the flow and then checking — it's like having a bouncer at the door rather than security guards inside checking every visitor.
Even with the "Only with Attachments" filter, you might want to add a safeguard. Not every email with an attachment is a sales report. Let's add a condition to check that the email subject contains a specific keyword.
Click New step and search for Condition. Select it.
In the condition configuration:
This means: only proceed if the email subject contains "Sales Report." Anything else will fall through to the "If no" branch, which you can leave empty for now (or add a "Send me an email notification" action later to alert you about unexpected emails that slipped through).
All subsequent steps will live in the If yes branch.
Inside the If yes branch, click Add an action. Search for and select "Apply to each" — this is a loop action that iterates over a list. You need it because an email can have multiple attachments.
When prompted for the output from previous steps, select Attachments from the dynamic content panel. This is the list of attachment objects that the email trigger collected.
Now, inside the Apply to each loop, add a new action: "Get attachment (V2)" from the Outlook connector.
Configure it like this:
This action retrieves the actual binary content of the attachment. The trigger only told you "there is an attachment named X." This action goes and actually fetches the file.
Warning: There's an important distinction between
Attachments(a list of metadata) and the content retrieved by "Get attachment (V2)" (the actual file data). Skipping the Get attachment step is a frequent beginner mistake that results in flows that reference attachment names but can't actually read the file contents.
Now we have the file content — but it's in a raw format called Base64, which is how binary data gets encoded for transmission over the internet. Think of Base64 like a transliteration — the file's bytes are represented as a string of text characters so they can be safely moved around. Before we can work with the data, we need to decode it back into readable text.
Inside the Apply to each loop, add a new action: Compose (found under the Data Operations connector). In the Inputs field, use this expression:
decodeBase64(outputs('Get_attachment_(V2)')?['body/contentBytes'])
Click on the expression tab in the dynamic content panel (not the dynamic content tab) and paste this in. This expression takes the Base64-encoded content bytes from the Get attachment action and decodes them into a plain text string.
Now add another action: "Create CSV table" — wait, actually, we're going the other direction. We have CSV text and want to parse it into rows. Search for and add the "Parse JSON" action.
Actually, for CSV specifically, Power Automate has a cleaner built-in path. Instead of Parse JSON, add the action "Create HTML table" — no, let's be precise about the best approach.
The cleanest production method for CSV parsing in Power Automate is to use the split() function to break the decoded text into rows, and then split each row on commas to get individual fields. Here's how to implement that with a Compose action and variables.
First, add a Compose action and use this expression to split the CSV into an array of lines:
split(outputs('Compose'), decodeUriComponent('%0A'))
This splits the decoded text on newline characters (\n), giving you an array where each element is one row of the CSV.
Tip: CSV files sometimes use
\r\n(carriage return + newline) as line endings, especially when created on Windows. If your array includes empty items or\rcharacters at the end of values, usedecodeUriComponent('%0D%0A')as your delimiter instead, or add areplace()call to strip carriage returns first:replace(outputs('Compose'), decodeUriComponent('%0D'), '').
Now add another Apply to each loop (nested inside the first one). Set its input to the output of the Compose action that split your CSV into lines.
Inside this inner loop, add a Condition to skip the header row. Set it up like this:
iterationIndexes('Apply_to_each_2') — this is the current loop index (0-based)0This skips index 0, which is the header row of the CSV. All subsequent rows (index 1, 2, 3, etc.) pass through.
In the If yes branch of this condition, add a Compose action to split the current row into individual columns:
split(items('Apply_to_each_2'), ',')
This gives you an array of values for the current row. Index 0 is Region, index 1 is Submitted By, and so on — matching the column order in your CSV.
Now add the action "Add a row into a table" from the Excel Online (Business) connector. Configure it:
Sales Reports/Consolidated_Sales.xlsx fileOnce the table is selected, Power Automate will show input fields for each column. Fill them in using expressions that pull values from your split array:
outputs('Compose_3')[0]outputs('Compose_3')[1]outputs('Compose_3')[2]outputs('Compose_3')[3]outputs('Compose_3')[4]outputs('Compose_3')[5]Replace Compose_3 with whatever name Power Automate automatically assigned to that Compose action — you can rename actions by clicking the three-dot menu on the action card and selecting "Rename."
Before sending a real email, use the Test feature in Power Automate. Click the Test button in the top right of the designer and select Manually. Then send yourself an email with "Sales Report" in the subject line and attach a simple CSV file.
Here's a sample CSV you can create in Notepad or any text editor and save as weekly_report.csv:
Region,Submitted By,Report Date,Product,Units Sold,Revenue
Northeast,Jamie Chen,2024-01-15,Widget Pro,142,28400
Southeast,Marcus Rivera,2024-01-15,Widget Pro,98,19600
Midwest,Sarah Kowalski,2024-01-15,Widget Lite,210,21000
West,Devon Patel,2024-01-15,Widget Pro,175,35000
After sending the email, watch the flow run in real time in the test panel. Each step will show a green checkmark on success or a red X on failure, along with the inputs and outputs at that step. This visibility is one of Power Automate's greatest strengths for debugging.
Now that you've followed the main walkthrough, extend your flow with two improvements:
Exercise 1: File Type Filter
Before parsing the attachment, add a condition that checks whether the attachment name ends with .csv. Use the endsWith() expression function on the attachment Name property. If it's not a CSV, add a Teams notification (or email to yourself) saying "Unexpected file type received: [filename]." This prevents your flow from crashing when someone accidentally attaches a PDF instead of a CSV.
Exercise 2: Duplicate Prevention
The current flow will add duplicate rows if the same email is processed twice (which can happen if a flow is manually re-triggered during testing). Add a step before the "Add a row into a table" action that uses "List rows present in a table" from the Excel connector to check whether a row with the same Region and Report Date already exists. Use a filter query like Region eq 'Northeast' and Report_Date eq '2024-01-15'. If the result count is greater than zero, skip the insert. This is a foundational pattern in any data pipeline — idempotency, meaning running the same operation twice produces the same result as running it once.
"My flow triggers but no data appears in Excel." The most common cause is that "Include Attachments" was left set to "No" in the trigger. The flow runs, but the Attachments property is empty. Go back to your trigger settings and make sure Include Attachments is Yes. You'll also need to re-test with a new email after changing this setting.
"I'm getting a Base64 decode error."
This usually means the attachment content bytes field path in your expression is wrong. Double-check that you're using outputs('Get_attachment_(V2)')?['body/contentBytes'] and that the action name matches exactly (spaces become underscores in expression names). Use the Test panel to inspect the raw output of the Get attachment step to confirm the field name.
"My rows are splitting incorrectly — values are running together or being cut short."
Your CSV probably has values that contain commas (like "Revenue, USD") or uses a different delimiter like a tab or semicolon. Open the raw CSV file in a text editor and verify the actual delimiter character. Replace the comma in your split expression with the correct delimiter.
"The flow is running but skipping some rows." Check your header-skip condition. If your CSV has more than one header row, or has a blank first line, the index offset will be wrong. Inspect the output of your line-splitting Compose action in the test run to see exactly what each index contains.
"Excel says the table can't be found."
This means the table name in your Excel connector action doesn't exactly match the name of the table in your workbook. Table names are case-sensitive in some contexts. Open the Excel file, click on the table, and verify the exact name shown in the Table Design tab. Common mismatch: you named it SalesData but the connector is looking for Table1.
You've built something genuinely useful. Let's recap the core patterns you've internalized:
decodeBase64()split() to decompose flat text into arrays of rows and valuesThis pattern — trigger, extract, parse, store — is the foundation of almost every data collection automation you'll ever build. The specific connectors and file formats will change, but the architecture stays the same.
Where to go from here:
reports@company.com inbox, you can point your trigger there instead of your personal inbox.The manual Monday morning data collection ritual is officially optional. Your flow handles it now.