mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-24 02:09:26 +08:00
chore(ai-builder): Move polling duplicate rules into the Gmail and Outlook trigger hints (no-changelog)
The trigger builder hints now carry the full rule: why polled emails come back, why an unread filter alone does nothing, and that the mark step runs after the record is created. The workflow-builder skill keeps one generic sentence for other polling triggers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
992b63f48f
commit
17db677cf4
@@ -742,21 +742,11 @@ Follow these rules strictly when generating workflows:
|
||||
upstream of the data producer, or have B reference `$('Data Node')`
|
||||
explicitly.
|
||||
8. A polling trigger (Gmail Trigger, Outlook Trigger, or similar) feeding an
|
||||
action that creates or writes records must ensure each polled item is
|
||||
processed once — poll cursors are best-effort bookkeeping (they reset when
|
||||
the trigger node is recreated or renamed) and every still-matching item is
|
||||
then re-delivered as a duplicate. Either restrict the trigger to
|
||||
unread/unprocessed items AND mark each item handled once its record exists,
|
||||
in a way the trigger's own filter excludes — mark as read when filtering
|
||||
unread, move out of the watched folder, or apply a label only if the
|
||||
trigger's query also excludes that label (a label does not mark a message
|
||||
read) — or record handled ids in a Data Table: look the id up before
|
||||
creating the record, skip ids already seen, and insert it only after the
|
||||
create succeeds. An unread filter alone is not enough: if no step ever
|
||||
marks the item read, it never excludes anything. Wire the mark-as-handled
|
||||
step AFTER the record-creating node, so a mid-run failure cannot consume an
|
||||
item without producing its output — this trades a rare duplicate (create
|
||||
succeeded, marking failed) for never losing an item; do not invert it.
|
||||
action that creates or writes records must process each polled item once:
|
||||
poll cursors reset, and still-matching items come back as duplicates. Mark
|
||||
each item handled in a way the trigger's own filter excludes, or record
|
||||
handled ids in a Data Table, and do it after the record-creating node. The
|
||||
trigger's `@builderHint` has the details for that service.
|
||||
|
||||
## Tool Naming Rules
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ export class GmailTrigger implements INodeType {
|
||||
},
|
||||
builderHint: {
|
||||
searchHint:
|
||||
'When downstream nodes create records (tasks, rows, tickets) per email, guarantee each email is processed exactly once: filter to unread AND mark each email read/labelled after its record is created, or track handled message ids in a Data Table. Otherwise the same email can be reprocessed into duplicates.',
|
||||
"When downstream nodes create records (tasks, rows, tickets) per email, guarantee each email is processed exactly once. Poll cursors reset when the trigger is recreated or renamed, and every still-matching email is then delivered again. Either filter to unread AND mark each email read, or apply a label only if the trigger's search query excludes that label (a label does not mark an email read); or track handled message ids in a Data Table: skip ids already seen and insert an id only after its record is created. An unread filter alone is not enough: if no step marks the email read, it excludes nothing. Wire the mark-as-handled step after the record-creating node, so a failed run never consumes an email without its record.",
|
||||
relatedNodes: [
|
||||
{
|
||||
nodeType: 'n8n-nodes-base.gmail',
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { GmailTrigger } from '../GmailTrigger.node';
|
||||
|
||||
const searchHint = new GmailTrigger().description.builderHint?.searchHint ?? '';
|
||||
|
||||
describe('Gmail Trigger duplicate-processing hint', () => {
|
||||
it('marks an email handled only after its record exists', () => {
|
||||
expect(searchHint).toContain('after the record-creating node');
|
||||
});
|
||||
|
||||
it('says an unread filter alone excludes nothing', () => {
|
||||
expect(searchHint).toContain('An unread filter alone is not enough');
|
||||
});
|
||||
|
||||
it('says a label only works when the trigger query excludes it', () => {
|
||||
expect(searchHint).toContain("only if the trigger's search query excludes that label");
|
||||
});
|
||||
});
|
||||
@@ -27,7 +27,7 @@ export class MicrosoftOutlookTrigger implements INodeType {
|
||||
},
|
||||
builderHint: {
|
||||
searchHint:
|
||||
'When downstream nodes create records (tasks, rows, tickets) per email, guarantee each email is processed exactly once: filter to unread AND mark each email read or move it to a folder after its record is created, or track handled message ids in a Data Table. Otherwise the same email can be reprocessed into duplicates.',
|
||||
'When downstream nodes create records (tasks, rows, tickets) per email, guarantee each email is processed exactly once. Poll cursors reset when the trigger is recreated or renamed, and every still-matching email is then delivered again. Either filter to unread AND mark each email read or move it out of the watched folder; or track handled message ids in a Data Table: skip ids already seen and insert an id only after its record is created. An unread filter alone is not enough: if no step marks the email read, it excludes nothing. Wire the mark-as-handled step after the record-creating node, so a failed run never consumes an email without its record.',
|
||||
relatedNodes: [
|
||||
{
|
||||
nodeType: 'n8n-nodes-base.microsoftOutlook',
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { MicrosoftOutlookTrigger } from '../../MicrosoftOutlookTrigger.node';
|
||||
|
||||
const searchHint = new MicrosoftOutlookTrigger().description.builderHint?.searchHint ?? '';
|
||||
|
||||
describe('Microsoft Outlook Trigger duplicate-processing hint', () => {
|
||||
it('marks an email handled only after its record exists', () => {
|
||||
expect(searchHint).toContain('after the record-creating node');
|
||||
});
|
||||
|
||||
it('says an unread filter alone excludes nothing', () => {
|
||||
expect(searchHint).toContain('An unread filter alone is not enough');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user