A project manager I worked with sent the same email every Monday: open the project workbook, copy the table, paste it into Outlook and fix the formatting that broke. It took 15 minutes each week, and the table never looked the same twice.
Power Automate Desktop can do the whole thing in seconds. There’s no action that drops an Excel table into an email, so the trick is to build an HTML table from the Excel data and send it with the body set to HTML. In this tutorial, I’ll show you every step.
Quick answer: Use Launch Excel and Read from Excel worksheet with First line of range contains column names turned on. Build an HTML table in a text variable with two For each loops and Append line to text (header cells from %ExcelData.Columns%, data cells from %CurrentItem[CurrentColumns]%), then use Send email through Outlook with Body is HTML turned on.
The example workbook
My workbook, Project_Details.xlsx, has one sheet with a header row and ten projects:
| Project Name | Start Date | End Date | Status | Project Manager |
|---|---|---|---|---|
| Website Redesign | 01/12/2026 | 03/16/2026 | In Progress | Megan Bowen |
| Mobile App Launch | 02/16/2026 | 04/30/2026 | Completed | Alex Wilber |
| CRM Integration | 03/02/2026 | 05/11/2026 | In Progress | Adele Vance |
The flow works with any table, because it reads the column names from the header row instead of hard-coding them. If you’re new to reading Excel files, see how to extract data from Excel using Power Automate Desktop.
Step 1: Open the workbook
Create a new desktop flow and add Launch Excel. Set Launch Excel to and open the following document, and browse to the file. Turn off Make instance visible if you don’t want the Excel window to open while the flow runs. The action produces %ExcelInstance%.

Step 2: Find where the data ends
Add Get first free column/row from Excel worksheet. It returns %FirstFreeColumn% and %FirstFreeRow%, the first empty column and row after your data.

You can skip this step if you choose All available values from worksheet in the next action, which reads everything in one go. I use the range version when the sheet has notes below the table. For other ways to count, see how to count rows and columns in a spreadsheet.
Step 3: Read the table
Add Read from Excel worksheet with these settings:
- Retrieve: Values from a range of cells.
- Start column: A and Start row: 1.
- End column:
%FirstFreeColumn - 1%and End row:%FirstFreeRow - 1%. - Expand Advanced and turn on First line of range contains column names.

That last setting matters most. Without it, the column names come through as Column1, Column2 and so on, and your header row shows up as the first data row in the email. The data lands in a data table named %ExcelData%.
To learn more about this variable type, see how to get values from a data table in Power Automate Desktop.
Step 4: Close Excel
You have the data, so add Close Excel with Before closing Excel set to Do not save document.

Step 5: Start the HTML table
Add Set variable, name the variable TableBody, and set its value to the opening tags. The <tr> opens the header row:
<table border="1" cellpadding="6" style="border-collapse:collapse; font-family:Segoe UI, Arial">
<tr>
Email clients ignore most CSS, so I keep the styling inline and simple. If variables in desktop flows are new to you, see variables in Power Automate Desktop.
Step 6: Add the header row
Add a For each loop with Value to iterate set to %ExcelData.Columns%, and rename the loop variable to ExcelColumns. This loop runs once per column name.

Inside the loop, add Append line to text. Set Original text to %TableBody%, Line to append to <th>%ExcelColumns%</th>, and store the result back in TableBody:

After the loop, add one more Append line to text with </tr> to close the header row:

Step 7: Add one row per project
Add a second For each with Value to iterate set to %ExcelData%. Each %CurrentItem% is one data row:

Inside it, append <tr> to open the row:

Then add a nested For each over %ExcelData.Columns%, with the loop variable CurrentColumns:

Inside the nested loop, append a data cell. %CurrentItem[CurrentColumns]% reads the current row’s value for the current column:
<td>%CurrentItem[CurrentColumns]%</td>

After the nested loop, but still inside the row loop, append </tr> to close the row:

For more data table tricks, like adding a row before you send it, see how to insert a row into a data table.
Step 8: Close the table
After both loops, add a last Append line to text with </table>. That’s all TableBody needs: the email action adds the rest of the message around it. Here’s the whole flow:

Step 9: Send the email
Add Launch Outlook, which produces %OutlookInstance%:

Then add Send email through Outlook. Pick the account, set Send email from to Account (or Other mailbox to send as a shared mailbox), and fill in To and Subject. Turn on Body is HTML, and use this body:
Hi,<br><br>
Here are the latest project details:<br><br>
%TableBody%
<br>Regards,<br>
Project Management Team

Finish with Close Outlook, which closes the Outlook instance the flow started:

Run the flow, and the email arrives with a neat table of all ten projects. To attach the workbook as well, see how to send an Outlook email with attachments.
One important limit: the Outlook actions in Power Automate Desktop work with classic Outlook for Windows, not the new Outlook for Windows. If your PC only has the new Outlook, use the Send email action in the Email group, which sends through an SMTP server, with Body is HTML turned on.
Doing the same in a cloud flow
If your workbook lives in OneDrive or SharePoint, a cloud flow is simpler: List rows present in a table followed by Create HTML table builds the table for you. See how to create an HTML table in Power Automate.
In cloud flows, the current item of an Apply to each loop is items('Apply_to_each'), and inside Select or Create HTML table it’s item(). That’s the cloud version of CurrentItem; see how to get the current item in Apply to each.
Troubleshooting
- The headers say Column1, Column2: turn on First line of range contains column names in Read from Excel worksheet.
- The email shows the HTML code as text: turn on Body is HTML in the send action.
- The table has no borders: check the opening tag in TableBody. Some email clients ignore CSS, so keep border=”1″.
- Dates show with a time: in the Read action’s Advanced options, set Get cell(s) contents as Formatted text values.
- Outlook actions fail: make sure classic Outlook is installed and signed in, or switch to the SMTP Send email action.
These Power Automate Desktop tutorials go well with this one:
Frequently Asked Questions
How do I put an Excel table in an email with Power Automate Desktop?
Read the range into a data table, build an HTML table with For each loops and Append line to text, and send it with Body is HTML turned on. The Excel actions are described in Microsoft’s Excel actions reference.
What does CurrentItem[CurrentColumns] mean?
CurrentItem is the current data row of the outer loop, and CurrentColumns is the current column name of the inner loop, so together they return one cell value.
Why does my HTML table show Column1 and Column2 as headers?
First line of range contains column names is off in Read from Excel worksheet. Turn it on and run the flow again.
Does Power Automate Desktop work with the new Outlook?
The Outlook actions need classic Outlook for Windows. With the new Outlook, use the SMTP Send email action instead.

I have worked with Microsoft technologies for more than 18 years, including SharePoint, Microsoft 365 and the Power Platform (Power Apps, Power Automate and Power BI), at companies like HP, TCS and KPIT. I started EnjoySharePoint in 2011 to share the solutions I build in real projects with readers in the United States, Canada, the United Kingdom, Australia, New Zealand and beyond. Microsoft has recognized this work with the Microsoft MVP award 13 times since 2014.