Power Automate

Group Excel Rows by Email and Send a CSV Attachment with Power Automate

Read an Excel table, find unique recipients, filter each person's rows and send a CSV attachment with Power Automate, with duplicate and pagination safeguards.

Collab365 Team · Published 23 April 2026 · Refreshed 24 August 2026 · 6 min read

Power Automate can read rows from an Excel table, group them by email address and send each recipient only their rows. The dependable no-code output is a CSV attachment, not a true Excel workbook.

The flow is:

  1. List the rows in the Excel table.
  2. Select the email values.
  3. Remove duplicates.
  4. Loop through the unique addresses.
  5. Filter the original rows for the current address.
  6. Create a CSV table.
  7. Send the CSV as an email attachment.

Fact-checked against current Microsoft Learn and connector documentation on 24 August 2026.

Before you start

You need:

  • an .xlsx workbook stored in OneDrive for Business, SharePoint or an Office 365 Group;
  • source data formatted as an Excel table, not a loose cell range;
  • a column containing one recipient email address per row;
  • permission to read the workbook and send through the selected mailbox;
  • representative test data, including blanks, duplicate addresses and mixed letter case.

Use simple table headers such as OwnerEmail, Customer, Reference and Amount. Rename the actions in the flow so the expressions remain understandable.

Choose a safe trigger

For a regular report, use Recurrence. For an operator-controlled report, use a manual trigger. A SharePoint file-modified trigger can fire for metadata changes and repeated edits, so it needs strict trigger conditions and an idempotency record if sending duplicate email would be harmful.

Do not add a five-minute Delay and claim that it solves workbook locks. Microsoft documents that the Excel connector can leave a file locked for a period after use and discourages simultaneous modifications. A blind delay cannot prove the workbook is ready.

If another process refreshes the workbook, make that process write a completion marker that this flow can check, or schedule this flow after the refresh window and handle connector failures explicitly.

Step 1: list the rows in the Excel table

Add Excel Online (Business) > List rows present in a table and select:

  • Location;
  • Document Library;
  • File;
  • Table.

Microsoft says the action returns 256 rows by default. Turn on pagination in the action settings when the table can exceed that. Set a threshold appropriate to the expected and tested data volume.

The Excel connector also warns that filtered or sorted data may not be complete when pagination is not enabled. Check the current connector reference before relying on OData options in a large production workbook.

Step 2: create a clean list of email addresses

Add a Select data operation and rename it Select_email_addresses.

Switch the action to text mode and select the email field from the Excel rows. The intended output is a simple array such as:

[
  "alex@contoso.com",
  "sam@contoso.com",
  "alex@contoso.com"
]

Normalise addresses before deduplication when source data is inconsistent. A typical expression is:

toLower(trim(item()?['OwnerEmail']))

The exact expression context depends on the action's mapping mode. Confirm the Select output in a test run before building the rest of the flow.

Step 3: remove blanks and duplicates

Use Filter array to remove empty values. Then add a Compose action named Unique_email_addresses with:

union(body('Filter_valid_email_addresses'), body('Filter_valid_email_addresses'))

Power Automate's union() expression returns a collection with no duplicates. Passing the same array twice is a common way to deduplicate it.

This is not email validation. It removes exact duplicates after normalisation. If bad addresses are possible, validate them against an approved business rule or route them to an exception report.

Step 4: loop through recipients

Add Apply to each and use the output from Unique_email_addresses.

Keep concurrency off initially. Parallel email creation can increase connector pressure and makes duplicate or ordering problems harder to diagnose. Enable concurrency only after testing the business and connector limits.

Inside the loop, add Filter array:

  • From: the value output from List rows present in a table;
  • condition: the normalised row email equals the current loop item.

In advanced mode, the comparison follows this pattern:

@equals(toLower(trim(item()?['OwnerEmail'])), items('Apply_to_each'))

Action names affect expression references. Insert dynamic content where possible and inspect the saved expression.

Step 5: shape the attachment rows

The filtered Excel objects can contain internal or unwanted columns. Add another Select action inside the loop and map only the columns the recipient should receive.

For example:

Output heading Source value
Customer Customer
Reference Reference
Amount Amount

This is also a data-protection boundary. Do not pass the complete source row into the attachment unless every field is appropriate for every recipient.

Step 6: create the CSV

Add Create CSV table and pass the shaped array from the previous Select action.

Choose automatic columns for a quick prototype or custom columns when you need stable headings and order. Test values containing commas, quotation marks and line breaks.

The output is CSV text. Giving it an .xlsx filename does not convert it into an Excel workbook. Use a .csv filename and a CSV content type.

Step 7: send the email

Add Office 365 Outlook > Send an email (V2) inside the loop.

Set:

  • To: current unique email address;
  • Subject: a clear, non-sensitive report name;
  • Body: explain the scope and reporting period;
  • Attachment Name: a safe .csv name;
  • Attachment Content: output from Create CSV table.

A safe filename expression can use a fixed prefix and UTC date:

concat('records-', formatDateTime(utcNow(), 'yyyy-MM-dd'), '.csv')

Do not put an email address into the filename unless your retention and privacy rules allow it.

Prevent duplicate sends

A flow that sends reports needs an idempotency design, especially when it uses a file-modified trigger.

Record a batch key such as:

<source file ID>|<source modified time>|<recipient>

Before sending, check whether that key already completed. After a successful send, store it. If the email action fails, retain enough information to retry without resending completed recipients.

For a small workflow, a SharePoint list can hold the batch records. For a larger process, choose a store that supports the required volume, concurrency and retention.

What if the attachment must be a real Excel workbook?

CSV is appropriate when recipients need rows and columns without formulas, formatting, worksheets or Excel data types.

If they need a true .xlsx file, use a different design:

  1. keep a blank workbook template in SharePoint;
  2. copy it once per recipient;
  3. populate a table in the copy, often with an Office Script or carefully controlled Excel actions;
  4. wait for confirmed completion rather than an arbitrary delay;
  5. retrieve the file content and attach the .xlsx file;
  6. clean up temporary files according to policy.

That design has more moving parts and workbook-lock risk. Test it at realistic volume. Do not simply rename CSV bytes to .xlsx.

Common failures

Only 256 rows are processed

Enable pagination on List rows present in a table and choose a tested threshold.

A recipient receives no rows

Compare normalised values. Leading spaces and letter case often make visually identical addresses different strings.

Several duplicate emails are sent

Check trigger duplication, the unique-address output, concurrency and the batch record. Deduplication inside one run does not protect against two separate runs.

The workbook is locked

Remove simultaneous writers, separate refresh from distribution and retry only appropriate transient failures. Microsoft warns against concurrent modifications to one Excel file.

The attachment opens with a warning

Make sure the extension matches the content. CSV output must use .csv, not .xlsx.

Microsoft sources

Review the design before sending real data

Bring a redacted sample table and the flow's Select and Filter outputs to the Power Automate Builders Space. The campaign attribution is unique to this article.