Below is a copy/paste, click-by-click build recipe for a Power Automate flow that detects new modern SharePoint list item comments and sends notifications without duplicates.
This design is based on the fact that modern Comments are not a normal column change, so we use “Send an HTTP request to SharePoint” (REST) when the standard connector can’t do it. (Microsoft Learn)
(And yes—comments can be disabled by admins, which affects the UI and your automation expectations. (Microsoft Support))
Workflow A (Recommended): Scheduled polling + REST /comments + state list
0) Create the helper list (state store)
Create a new SharePoint list (same site is easiest) named:
Comment Notification State
Columns (besides default Title):
- ListTitle (Single line of text)
- ItemId (Number)
- LastCommentId (Number)
- LastCommentTime (Date and time)
- LastRun (Date and time)
- NotifiedTo (Single line of text) (optional)
Title will store a unique key like:Example List|123
This is what prevents duplicate notifications.
1) Create the flow
Trigger
Recurrence
- Interval:
5 - Frequency:
Minute
2) Get the target items (from your business list)
Get items (SharePoint)
- Site Address:
https://contoso.sharepoint.com/sites/ExampleSite - List Name:
Example List - (Optional but recommended) Filter Query example:
Status eq 'Active'
- (Optional) Top Count:
200(or whatever makes sense)
This keeps the scan small and fast.
3) For each item → retrieve comments via REST
Add: Apply to each
Input: value from Get items
Inside the loop:
3.1) HTTP GET comments
Add action: Send an HTTP request to SharePoint (Microsoft Learn)
- Site Address:
https://contoso.sharepoint.com/sites/ExampleSite - Method:
GET - Uri:
_api/web/lists/getbytitle('Example List')/items(@{items('Apply_to_each')?['ID']})/comments
This “comments live under Items()” pattern is the key. (Stack Overflow)
- Headers:
- Key:
Accept - Value:
application/json;odata=nometadata
- Key:
4) Parse comments response
Add Parse JSON
- Content:
body('Send_an_HTTP_request_to_SharePoint')
Schema (safe minimal)
Use this schema (works for common responses; if your tenant differs, generate schema from a sample run):
{ "type": "object", "properties": { "value": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "integer" }, "text": { "type": "string" }, "createdDate": { "type": "string" }, "author": { "type": "object", "properties": { "email": { "type": "string" }, "name": { "type": "string" } } } }, "required": [ "id" ] } } }}
5) Short-circuit: if there are no comments, skip
Add Condition:
Left expression:
length(body('Parse_JSON')?['value'])
Operator: is equal to
Right: 0
If yes (0): do nothing (or just update LastRun if you want).
If no: continue.
6) Compute “latest comment” reliably
6.1) Select comment IDs
Add Select
- From:
body('Parse_JSON')?['value'] - Map:
- Key:
id - Value:
item()?['id']
- Key:
6.2) Latest comment ID
Add Compose → name it LatestCommentId
Expression:
max(body('Select'))
This avoids assuming the API returns comments sorted.
6.3) Get the latest comment object
Add Filter array
- From:
body('Parse_JSON')?['value'] - Condition:
- Left:
item()?['id'] - Operator:
is equal to - Right:
outputs('LatestCommentId')
- Left:
Add Compose → name it LatestComment
Expression:
first(body('Filter_array'))
Now you can reference:
- Latest text:
outputs('LatestComment')?['text'] - Latest time:
outputs('LatestComment')?['createdDate'] - Latest author name/email:
outputs('LatestComment')?['author']?['name'],...['email']
7) Read state for this item (prevent duplicates)
Add Get items (from state list)
- Site Address: same site
- List Name:
Comment Notification State - Filter Query:
Title eq 'Example List|@{items('Apply_to_each')?['ID']}' - Top Count:
1
Add Condition: “State exists?”
Left:
length(body('Get_items_-_State')?['value'])
Operator: is equal to
Right: 0
7.A) If no state exists (first time we see this item)
Create item (in state list)
- Title:
Example List|@{items('Apply_to_each')?['ID']} - ListTitle:
Example List - ItemId:
@{items('Apply_to_each')?['ID']} - LastCommentId:
@{outputs('LatestCommentId')} - LastCommentTime:
@{outputs('LatestComment')?['createdDate']} - LastRun:
@{utcNow()}
✅ And do not send a notification on first discovery (recommended), otherwise you’ll spam for old comments.
7.B) If state exists, compare and notify only if new
Add Compose → name it StateItem
Expression:
first(body('Get_items_-_State')?['value'])
Add Condition: “Is there a new comment?”
Left:
outputs('LatestCommentId')
Operator: is greater than
Right:
outputs('StateItem')?['LastCommentId']
If NO (not greater): do nothing (optional update LastRun only).
If YES: send notification + update state
8) Send the notification
8.1) Build item link
If your Get items output already provides Link to item, use it.
If not, create a link like:
Add Compose → name it ItemLink
concat( 'https://contoso.sharepoint.com/sites/ExampleSite', '/Lists/Example%20List/DispForm.aspx?ID=', string(items('Apply_to_each')?['ID']))
8.2) Recipients
Common options:
- Created By Email (from item)
- Assigned To email (Person field)
- Watchers field (multi-person)
- A fixed mailbox or Teams channel
Example (Created By):
Use the dynamic content Created By Email (often Author Email) from SharePoint Get items output.
8.3) Send email
Action: Send an email (V2)
Subject example:
New comment on: @{items('Apply_to_each')?['Title']}
Body example (HTML is fine):
<p><b>List:</b> Example List</p><p><b>Item:</b> @{items('Apply_to_each')?['Title']}</p><p><b>Comment by:</b> @{outputs('LatestComment')?['author']?['name']} (@{outputs('LatestComment')?['author']?['email']})</p><p><b>When:</b> @{outputs('LatestComment')?['createdDate']}</p><p><b>Comment:</b><br/>@{outputs('LatestComment')?['text']}</p><p><a href="@{outputs('ItemLink')}">Open item</a></p>
Note: SharePoint already supports @mentions in comments and can notify mentioned users. (Microsoft Support)
Your flow is typically for non-mention routing (e.g., notify assignee/owner/watchers).
9) Update the state record (critical)
Action: Update item (in state list)
- ID (of the state item):
outputs('StateItem')?['ID'] - LastCommentId:
outputs('LatestCommentId') - LastCommentTime:
outputs('LatestComment')?['createdDate'] - LastRun:
utcNow() - NotifiedTo (optional):
- set to the recipient(s) you used
Workflow hardening tips (do these)
A) Avoid false positives / old comments
The “first time state creation = no notify” rule prevents initial spam.
B) Throttling / performance
- Keep your Get items filtered (Active items only).
- Increase the recurrence to 10–15 minutes if the list is large.
C) Why we don’t use “Get changes for an item”
Because it detects property/column changes, not comment-thread changes. (Microsoft Learn)
D) REST fundamentals
Your REST approach follows Microsoft’s documented SharePoint REST patterns for list/item access. (Microsoft Learn)
Final summary tables
Build steps
| Step | Action | Purpose |
|---|---|---|
| 1 | Recurrence | Poll on schedule |
| 2 | Get items (business list) | Choose which items to check |
| 3 | Send HTTP request (GET /comments) | Retrieve modern comments thread (Microsoft Learn) |
| 4 | Parse JSON | Work with comments array |
| 5 | Select + max() | Identify latest comment id |
| 6 | Get items (state list) | Load last processed comment |
| 7 | Compare ids | Detect only new comments |
| 8 | Send email/Teams | Notify |
| 9 | Update state | Prevent duplicates |
Key technical choices
| Choice | What we used | Why |
|---|---|---|
| Comment access | REST /items(ID)/comments | Comments aren’t standard fields (Stack Overflow) |
| Connector gap | “Send an HTTP request to SharePoint” | Microsoft’s recommended escape hatch (Microsoft Learn) |
| Duplicate prevention | State list with LastCommentId | Reliable, auditable |
| Change detection | Compare numeric comment IDs | Simple and robust |
