Building a Clean Microsoft Teams Notification Card from a SharePoint List with Power Automate

When a SharePoint list item is created or updated, a plain Teams message works, but it rarely looks professional. The message becomes a block of text, long URLs break the layout, and multiline fields lose visual structure.

A better pattern is to post an Adaptive Card from Power Automate. Microsoft documents Adaptive Cards in Power Automate as the recommended way to share structured information in Teams without relying on custom HTML or CSS. Adaptive Cards are authored in JSON and rendered as native UI inside the host application. (Microsoft Learn)

In this article, I will refactor the notification design using generic field names only, so the example is safe for documentation, reusable, and ready to adapt to any SharePoint list.


Why not use raw HTML or a classic message body?

This is the first architectural decision to get right.

In Teams, Adaptive Cards are the right option for structured workflow notifications. Microsoft states that Workflows support Adaptive Cards only and do not support the older MessageCard model used by legacy Office 365 connectors. Microsoft also notes that HTML is not supported in Adaptive Cards, while Markdown is supported in certain text fields such as TextBlock, Fact.Title, and Fact.Value. (Microsoft Learn)

That means this approach is not ideal:

<table>
<tr>
<td>Title</td>
<td>My task</td>
</tr>
</table>

Even if HTML appears in a Compose action, it does not mean the downstream Teams action will render it as a polished card. For a modern Teams notification, JSON-based Adaptive Cards are the better design. (Microsoft Learn)


The target architecture

The simplest production-friendly pattern is this:

SharePoint trigger
optional Compose actions for each field
Microsoft Teams: Post adaptive card in a chat or channel

This pattern keeps the flow readable and makes debugging much easier. Microsoft’s Power Automate documentation also recommends keeping cards simple and using simple blocks of data instead of overly complex table arrays. (Microsoft Learn)


Renaming all field names for a reusable article

To keep the article generic and reusable, I am replacing the original column names with neutral placeholders.

Original concept to generic article mapping

Original ideaGeneric article labelGeneric internal token example
TitleRequest TitleRequestTitle
DescriptionRequest DetailsRequestDetails
SharePoint Team ObservationsReview NotesReviewNotes
ModifiedLast UpdatedLastUpdated
Link to itemOpen Item LinkItemLink

This does not mean you must rename your real SharePoint columns in production. It simply means that, in the article and code sample, I am using clean placeholder names so the design can be understood without exposing project-specific schema.


Recommended card layout

For a task or request notification, the visual order should follow the way people read status updates:

  1. notification title
  2. main request title
  3. details
  4. review or support notes
  5. last updated timestamp
  6. button to open the list item

That sequence gives the message a business-friendly flow and works well in Teams chat.


The Adaptive Card design

Microsoft recommends Adaptive Cards in Teams for structured UI, and Teams design guidance also points to ColumnSet when you want a table-like arrangement or grid layout. For this scenario, however, a FactSet is simpler and works very well for label-value display. (Microsoft Learn)

Here is a clean, anonymized card using generic field names.

{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.4",
"msteams": {
"width": "Full"
},
"body": [
{
"type": "Container",
"style": "emphasis",
"bleed": true,
"items": [
{
"type": "TextBlock",
"text": "SharePoint Request Notification",
"weight": "Bolder",
"size": "Large",
"wrap": true
},
{
"type": "TextBlock",
"text": "A request was created or updated in the SharePoint list.",
"spacing": "Small",
"isSubtle": true,
"wrap": true
}
]
},
{
"type": "Container",
"spacing": "Medium",
"items": [
{
"type": "FactSet",
"facts": [
{
"title": "Request Title",
"value": "@{triggerOutputs()?['body/RequestTitle']}"
},
{
"title": "Request Details",
"value": "@{triggerOutputs()?['body/RequestDetails']}"
},
{
"title": "Review Notes",
"value": "@{triggerOutputs()?['body/ReviewNotes']}"
},
{
"title": "Last Updated",
"value": "@{triggerOutputs()?['body/LastUpdated']}"
}
]
}
]
}
],
"actions": [
{
"type": "Action.OpenUrl",
"title": "Open Request",
"url": "@{triggerOutputs()?['body/ItemLink']}"
}
]
}

A safer implementation with Compose actions

In real flows, I usually recommend isolating values with Compose actions before assembling the card. This makes troubleshooting easier, especially when a SharePoint internal name is not what you expected.

Use one Compose per value.

Compose_RequestTitle

@{triggerOutputs()?['body/RequestTitle']}

Compose_RequestDetails

@{triggerOutputs()?['body/RequestDetails']}

Compose_ReviewNotes

@{triggerOutputs()?['body/ReviewNotes']}

Compose_LastUpdated

@{triggerOutputs()?['body/LastUpdated']}

Compose_ItemLink

@{triggerOutputs()?['body/ItemLink']}

Then use the Compose outputs inside the card:

{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.4",
"msteams": {
"width": "Full"
},
"body": [
{
"type": "Container",
"style": "emphasis",
"bleed": true,
"items": [
{
"type": "TextBlock",
"text": "SharePoint Request Notification",
"weight": "Bolder",
"size": "Large",
"wrap": true
},
{
"type": "TextBlock",
"text": "A request was created or updated in the SharePoint list.",
"spacing": "Small",
"isSubtle": true,
"wrap": true
}
]
},
{
"type": "Container",
"spacing": "Medium",
"items": [
{
"type": "FactSet",
"facts": [
{
"title": "Request Title",
"value": "@{outputs('Compose_RequestTitle')}"
},
{
"title": "Request Details",
"value": "@{outputs('Compose_RequestDetails')}"
},
{
"title": "Review Notes",
"value": "@{outputs('Compose_ReviewNotes')}"
},
{
"title": "Last Updated",
"value": "@{outputs('Compose_LastUpdated')}"
}
]
}
]
}
],
"actions": [
{
"type": "Action.OpenUrl",
"title": "Open Request",
"url": "@{outputs('Compose_ItemLink')}"
}
]
}

This version is much easier to maintain.


Why this looks better in Teams

The improvement is not only aesthetic. It is architectural.

With an Adaptive Card:

  • the header is visually separated
  • labels and values are aligned cleanly
  • long URLs are hidden behind a button
  • multiline text is easier to read
  • the message looks like a workflow notification instead of a copied email fragment

Microsoft describes Adaptive Cards as a host-native way to display blocks of information without the complexity of custom CSS or HTML rendering. That is exactly why they fit this scenario so well. (Microsoft Learn)


Important note about SharePoint internal names

This is one of the most common pitfalls.

Your SharePoint display name might be:

Review Notes

But the internal name could be something else entirely, especially if the field was renamed after creation. For example, spaces may be encoded, or a legacy name may remain behind the scenes.

So in production, always validate the internal name before finalizing expressions. A practical way is:

  • insert the field once from Dynamic content
  • save and test the flow
  • inspect the run history output
  • confirm the exact property exposed by the trigger

That is why the Compose-first approach is often the safest option.


Suggested production pattern

For a stable implementation, I recommend this exact flow structure:

OrderActionPurpose
1When an item is created or modifiedDetect SharePoint changes
2Compose_RequestTitleIsolate the title
3Compose_RequestDetailsIsolate the long text field
4Compose_ReviewNotesIsolate the support or review notes
5Compose_LastUpdatedIsolate the timestamp
6Compose_ItemLinkIsolate the item URL
7Post adaptive card in a chat or channelSend the final Teams notification

This structure is simple, readable, and easier to debug than a single large expression block.


Design refinements you can add later

Once the card is working, you can evolve it in a controlled way.

A few good next steps are:

  • format the timestamp before sending it
  • truncate extremely long text fields
  • add an environment label such as Dev, Test, or Production
  • include a status field with a stronger visual emphasis
  • switch from FactSet to ColumnSet if you want a more custom visual grid

For most SharePoint-to-Teams notifications, though, the FactSet version is already clean and effective.


Final anonymized sample mapping

Below is the final neutral mapping used in this article:

Functional meaningCard labelPlaceholder field token
Main request nameRequest TitleRequestTitle
Main descriptionRequest DetailsRequestDetails
Team commentaryReview NotesReviewNotes
Last modification dateLast UpdatedLastUpdated
SharePoint item URLOpen RequestItemLink

Conclusion

If the goal is to send a beautiful and structured notification to Teams from a SharePoint-triggered Power Automate flow, the correct path is to use an Adaptive Card, not a raw HTML table and not a plain message body.

This gives you a cleaner interface, a more professional result, and a much more maintainable flow. It also aligns with Microsoft’s current guidance for Teams workflows, where Adaptive Cards are the supported modern card model. (Microsoft Learn)

Step summary table

StepWhat to doRecommended implementation
1Keep the SharePoint triggerWhen an item is created or modified
2Avoid raw HTML in the Teams messageDo not use <table>, <tr>, or <td> for this scenario
3Use a card-based Teams actionPost adaptive card in a chat or channel
4Rename fields for documentationUse placeholders such as RequestTitle, RequestDetails, ReviewNotes
5Build the layout in JSONAdaptive Card with FactSet
6Add a clean link experienceAction.OpenUrl button
7Improve maintainabilityUse Compose actions before the final card

Technical reference table

TopicKey pointSource
Adaptive Cards in Power AutomateAdaptive Cards are used to share information in Teams using JSONMicrosoft Learn (Microsoft Learn)
Creating cards in flowsKeep cards simple and prefer simple blocks of dataMicrosoft Learn (Microsoft Learn)
HTML supportHTML is not supported in Adaptive Cards in TeamsMicrosoft Learn (Microsoft Learn)
Markdown supportMarkdown is supported in TextBlock, Fact.Title, and Fact.ValueMicrosoft Learn (Microsoft Learn)
Teams workflow supportWorkflows support Adaptive Cards only, not legacy MessageCardsMicrosoft Learn (Microsoft Learn)
Card design layoutColumnSet can be used when a table-like layout is neededMicrosoft Learn (Microsoft Learn)
Edvaldo Guimrães Filho Avatar

Published by