Want to prevent duplicate email sending in next run

Hello, I am creating one workflow where m getting tickets of last 24 hours, and thn again filtering which are in new state older thn 30mnts, and thn it will send an email to the person to notify, so i am stuck at next trigger where its sending duplicate mails for those tickets which was sent earlier. can you please help me here. i am attaching my workflow
<{
“nodes”: [
{
“parameters”: {
“requestMethod”: “POST”,
“url”: “={{ $node["Radium Authentication"].json.url }}/itsm/get_all_tickets”,
“allowUnauthorizedCerts”: true,
“options”: {},
“bodyParametersUi”: {
“parameter”: [
{}
]
},
“headerParametersUi”: {
“parameter”: [
{
“name”: “Authorization”,
“value”: “={{ $node["Radium Authentication"].json.token }}”
}
]
},
“queryParametersUi”: {
“parameter”: [
{
“name”: “page”,
“value”: “1”
},
{
“name”: “offset”,
“value”: “100”
}
]
}
},
“id”: “662d916b-7edd-4a5f-9337-83b4c81b650b”,
“name”: “Get Credential list Request”,
“type”: “n8n-nodes-base.httpRequest”,
“typeVersion”: 2,
“position”: [
-1960,
280
]
},
{
“parameters”: {},
“id”: “bc263f6e-4b85-4544-9372-e9070205ee7f”,
“name”: “Radium Authentication”,
“type”: “n8n-nodes-base.RadiumDetails”,
“typeVersion”: 1,
“position”: [
-2180,
280
]
},
{
“parameters”: {
“jsCode”: “\n/**\n * Filters tickets that are:\n * - status NEW (case-insensitive),\n * - created within the last 24 hours,\n * - at least 30 minutes old.\n *\n * It parses created_on1 robustly and builds an ISO string with +05:30 (IST),\n * so comparisons are done on UTC epoch milliseconds reliably.\n /\n\nconst IST_TZ = ‘+05:30’;\nconst NOW_MS = Date.now();\nconst TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1000;\nconst THIRTY_MINUTES_MS = 30 * 60 * 1000;\nconst WINDOW_START_MS = NOW_MS - TWENTY_FOUR_HOURS_MS;\n\n// ---- Collect tickets from typical shapes across all incoming items ----\nfunction collectTickets(items) {\n const all = [];\n for (const it of items) {\n const root = it.json;\n\n if (Array.isArray(root)) {\n all.push(…root);\n continue;\n }\n if (Array.isArray(root?.response)) {\n all.push(…root.response);\n continue;\n }\n if (Array.isArray(root?.response?.response)) {\n all.push(…root.response.response);\n continue;\n }\n\n // Fallback: single ticket per item\n if (root && typeof root === ‘object’) {\n all.push(root);\n }\n }\n return all;\n}\n\n// ---- Parse "created_on1" to UTC epoch ms by building an ISO with IST offset ----\n// Supports: "MM/DD/YYYY, HH:MM:SS" or "DD/MM/YYYY, HH:MM:SS", optional "AM/PM"\n// Also supports "YYYY-MM-DD HH:MM:SS" and ISO-like strings.\nfunction parseIstToEpochMs(str) {\n if (typeof str !== ‘string’) return null;\n let s = str.trim();\n\n // If it’s already an ISO string, let Date.parse handle it.\n // e.g., "2025-12-24T10:30:00+05:30" or "2025-12-24 10:30:00"\n if (/^\d{4}-\d{2}-\d{2}/.test(s)) {\n // If no timezone is present, assume IST\n if (!/[zZ]|[+\-]\d{2}:\d{2}$/.test(s)) s = s.replace(’ ‘, ‘T’) + IST_TZ;\n const ms = Date.parse(s);\n return Number.isNaN(ms) ? null : ms;\n }\n\n // Split "date, time" OR "date time"\n let datePart, timePart;\n const byComma = s.split(’, ‘);\n if (byComma.length >= 2) {\n datePart = byComma[0];\n timePart = byComma.slice(1).join(’ ').trim();\n } else {\n // Try space split between date and time\n const m = s.match(/^(.+?)\s+(\d{1,2}:\d{2}:\d{2}(?:\s[AP]M)?)$/i);\n if (!m) return null;\n datePart = m[1];\n timePart = m[2];\n }\n\n // Determine separator and split date\n const sep = datePart.includes(‘/’) ? ‘/’ :\n datePart.includes(‘-’) ? ‘-’ :\n datePart.includes(‘.’) ? ‘.’ : ‘/’;\n const parts = datePart.split(sep).map(x => x.trim());\n\n let year, month, day;\n if (parts[0].length === 4) {\n // YYYY-MM-DD\n year = Number(parts[0]);\n month = Number(parts[1]);\n day = Number(parts[2]);\n } else {\n // Decide MM/DD/YYYY vs DD/MM/YYYY by first part value\n const a = parts.map(Number);\n if (a[0] > 12) { // DD/MM/YYYY\n day = a[0]; month = a[1]; year = a[2];\n } else { // MM/DD/YYYY\n month = a[0]; day = a[1]; year = a[2];\n }\n }\n\n // Parse time with optional AM/PM\n let tp = timePart.trim();\n let ampm = null;\n if (/AM$/i.test(tp)) { ampm = ‘AM’; tp = tp.replace(/AM$/i, ‘’).trim(); }\n else if (/PM$/i.test(tp)) { ampm = ‘PM’; tp = tp.replace(/PM$/i, ‘’).trim(); }\n\n const [hStr, mStr, sStr] = tp.split(‘:’);\n let hour = Number(hStr);\n const minute = Number(mStr);\n const second = Number(sStr);\n\n if ([year, month, day, hour, minute, second].some(n => Number.isNaN(n))) return null;\n\n // 12h → 24h\n if (ampm === ‘AM’ && hour === 12) hour = 0;\n if (ampm === ‘PM’ && hour !== 12) hour += 12;\n\n // Build ISO string with IST timezone\n const iso =\n ${String(year).padStart(4, '0')}- +\n ${String(month).padStart(2, '0')}- +\n ${String(day).padStart(2, '0')}T +\n ${String(hour).padStart(2, '0')}: +\n ${String(minute).padStart(2, '0')}: +\n ${String(second).padStart(2, '0')} +\n ${IST_TZ};\n\n const ms = Date.parse(iso);\n return Number.isNaN(ms) ? null : ms;\n}\n\n// ---- Main filter ----\nconst tickets = collectTickets(items);\nconst result = ;\n\nfor (const t of tickets) {\n const created =\n t?.created_on1 ?? t?.created_on ?? t?.CreatedOn ?? t?.createdAt;\n const statusRaw =\n t?.radium_incident_status1 ?? t?.radium_incident_status ??\n t?.status ?? t?.incident_status;\n\n if (!created || !statusRaw) continue;\n\n const status = String(statusRaw).trim().toLowerCase();\n if (status !== ‘new’) continue;\n\n const createdMs = parseIstToEpochMs(String(created));\n if (createdMs == null) continue;\n\n // Only last 24h (inclusive) and not future\n if (createdMs < WINDOW_START_MS || createdMs > NOW_MS) continue;\n\n const ageMs = NOW_MS - createdMs;\n if (ageMs < THIRTY_MINUTES_MS) continue;\n\n result.push({\n json: {\n radium_incident_id1: t.radium_incident_id1 ?? t.id ?? t.ticket_id,\n age_minutes: Math.floor(ageMs / 60000),\n status: statusRaw,\n created_on: created,\n },\n });\n}\n\nreturn result;\n”
},
“type”: “n8n-nodes-base.code”,
“typeVersion”: 2,
“position”: [
-1760,
280
],
“id”: “47b47c70-1912-4756-bf4c-d81eb6fd545d”,
“name”: “Code”,
“alwaysOutputData”: false
},
{
“parameters”: {
“conditions”: {
“options”: {
“caseSensitive”: true,
“leftValue”: “”,
“typeValidation”: “strict”,
“version”: 2
},
“conditions”: [
{
“id”: “69f637ba-02e3-4d92-a306-e67059a183f5”,
“leftValue”: “={{ $json.status }}”,
“rightValue”: “new”,
“operator”: {
“type”: “string”,
“operation”: “equals”,
“name”: “filter.operator.equals”
}
},
{
“id”: “931bb843-f4fb-4a6e-94d0-ab52e3ff271d”,
“leftValue”: “={{ $json.age_minutes }}”,
“rightValue”: 30,
“operator”: {
“type”: “number”,
“operation”: “gte”
}
}
],
“combinator”: “and”
},
“options”: {}
},
“type”: “n8n-nodes-base.if”,
“typeVersion”: 2.2,
“position”: [
-1560,
280
],
“id”: “ab567324-a52c-46b7-8f93-e59d4bea5ac7”,
“name”: “If”
},
{
“parameters”: {
“jsCode”: “return items.map(item => {\n const id = String(\n item.json.radium_incident_id1 ??\n item.json.Radium_ticket_id ??\n item.json.ticket_id ??\n item.json.id\n ).trim();\n\n return {\n json: {\n Radium_Ticket: ‘ITSM’,\n Radium_ticket_id: id,\n email_type: ‘ITSM_NEW_TICKET_ALERT’,\n ticket_id: item.json.ticket_id ?? id,\n created_on: item.json.created_on,\n age_minutes: item.json.age_minutes,\n dedup_id: id\n }\n };\n});\n”
},
“type”: “n8n-nodes-base.code”,
“typeVersion”: 2,
“position”: [
-1300,
260
],
“id”: “143f536b-f443-4a0a-acc6-7f246e15bc7d”,
“name”: “Code1”
},
{
“parameters”: {
“options”: {
“reset”: false
}
},
“type”: “n8n-nodes-base.splitInBatches”,
“typeVersion”: 3,
“position”: [
-660,
100
],
“id”: “0685fbd5-9a33-49fa-aaf4-6494feee0a3b”,
“name”: “Loop Over Items”
},
{
“parameters”: {
“requestMethod”: “POST”,
“url”: “={{$node["Radium Authentication"].json["url"]}}/send_mail”,
“allowUnauthorizedCerts”: true,
“options”: {},
“bodyParametersUi”: {
“parameter”: [
{
“name”: “toList”,
“value”: “={{ $json.to }}”
},
{
“name”: “subject”,
“value”: “={{ $json.subject }}”
},
{
“name”: “email_type”,
“value”: “={{ $json["emailType"] }}”
},
{
“name”: “message”,
“value”: “={{ $json.body }}”
},
{
“name”: “email_server_id”,
“value”: “1”
}
]
},
“headerParametersUi”: {
“parameter”: [
{
“name”: “Authorization”,
“value”: “={{$node["Radium Authentication"].json["token"]}}”
}
]
}
},
“id”: “edd21cbb-99b7-414b-930f-cbae8794acc4”,
“name”: “Sending Email2”,
“type”: “n8n-nodes-base.httpRequest”,
“typeVersion”: 2,
“position”: [
-420,
100
],
“alwaysOutputData”: true,
“continueOnFail”: true
},
{
“parameters”: {
“values”: {
“string”: [
{
“name”: “subject”,
“value”: “ITSM Ticket Alert”
},
{
“name”: “emailType”,
“value”: “={{ $json.email_type }}”
},
{
“name”: “to”,
“value”: “[email protected]
},
{
“name”: “=body”,
“value”: “=\nHello Team,

\nThis is an automated notification to inform you that a support ticket has been open for more than 30 minutes.

\n\nTicket Details:
\n• Ticket ID: {{$json["Radium_ticket_id"]}}
\n• Opened On: {{$json["created_on"]}}
\n• Open Duration: {{$json["age_minutes"]}} minutes

\n\nKindly review the ticket at the earliest and take necessary action.

\n\nRegards,
\nITSM Automation System

\nNote: This is a system-generated email. Please do not reply.\n”
},
{
“name”: “dedup_id”,
“value”: “=\n={{ \n String(\n $json.radium_incident_id1 ??\n $json.Radium_ticket_id ??\n $json.id ??\n $json.ticket_id\n ).trim()\n}}\n”
}
]
},
“options”: {}
},
“id”: “7cfc4886-0346-47a3-aed2-22558d942255”,
“name”: “Set prams email2”,
“type”: “n8n-nodes-base.set”,
“typeVersion”: 1,
“position”: [
-820,
260
]
},
{
“parameters”: {
“functionCode”: “const staticData = this.getWorkflowStaticData(‘global’);\nif (!staticData.sentTickets) {\n staticData.sentTickets = {};\n}\n\n// Optional: purge entries older than 7 days\nconst now = Date.now();\nconst TTL = 7 * 24 * 60 * 60 * 1000;\nfor (const [k, ts] of Object.entries(staticData.sentTickets)) {\n if (typeof ts === ‘number’ && (now - ts > TTL)) delete staticData.sentTickets[k];\n}\n\nreturn items.filter(item => {\n const id = String(item.json.dedup_id || ‘’).trim();\n return id && !staticData.sentTickets[id];\n});\n”
},
“id”: “7622f2fc-0efe-43aa-a4a4-798dcd4a945e”,
“name”: “Function2”,
“type”: “n8n-nodes-base.function”,
“typeVersion”: 1,
“position”: [
40,
240
]
},
{
“parameters”: {
“functionCode”: “const staticData = this.getWorkflowStaticData(‘global’);\n\nif (!staticData.sentTickets) {\n staticData.sentTickets = {};\n}\n\nreturn items.filter(item => {\n const dedupId = String(item.json.dedup_id).trim();\n return !staticData.sentTickets[dedupId];\n});”
},
“id”: “677f505e-a7b5-4d5b-9bbe-21e54e08bc68”,
“name”: “Function1”,
“type”: “n8n-nodes-base.function”,
“typeVersion”: 1,
“position”: [
-1040,
260
]
},
{
“parameters”: {
“conditions”: {
“options”: {
“caseSensitive”: true,
“leftValue”: “”,
“typeValidation”: “strict”,
“version”: 2
},
“conditions”: [
{
“id”: “20898851-bc34-40b7-abb0-913c82e34138”,
“leftValue”: “={{ $json.status }}”,
“rightValue”: “success”,
“operator”: {
“type”: “string”,
“operation”: “equals”
}
}
],
“combinator”: “and”
},
“options”: {}
},
“type”: “n8n-nodes-base.if”,
“typeVersion”: 2.2,
“position”: [
0,
0
],
“id”: “7a7e80f6-e884-4c02-8fd5-c83f6b246dbb”,
“name”: “If1”
},
{
“parameters”: {
“rule”: {
“interval”: [
{
“field”: “minutes”
}
]
}
},
“type”: “n8n-nodes-base.scheduleTrigger”,
“typeVersion”: 1.2,
“position”: [
-2440,
280
],
“id”: “99fcb4ab-0d7e-4e75-bda5-d34bbca51e39”,
“name”: “Schedule Trigger”
}
],
“connections”: {
“Get Credential list Request”: {
“main”: [
[
{
“node”: “Code”,
“type”: “main”,
“index”: 0
}
]
]
},
“Radium Authentication”: {
“main”: [
[
{
“node”: “Get Credential list Request”,
“type”: “main”,
“index”: 0
}
]
]
},
“Code”: {
“main”: [
[
{
“node”: “If”,
“type”: “main”,
“index”: 0
}
]
]
},
“If”: {
“main”: [
[
{
“node”: “Code1”,
“type”: “main”,
“index”: 0
}
]
]
},
“Code1”: {
“main”: [
[
{
“node”: “Function1”,
“type”: “main”,
“index”: 0
}
]
]
},
“Loop Over Items”: {
“main”: [
,
[
{
“node”: “Sending Email2”,
“type”: “main”,
“index”: 0
}
]
]
},
“Sending Email2”: {
“main”: [
[
{
“node”: “If1”,
“type”: “main”,
“index”: 0
},
{
“node”: “Loop Over Items”,
“type”: “main”,
“index”: 0
}
]
]
},
“Set prams email2”: {
“main”: [
[
{
“node”: “Loop Over Items”,
“type”: “main”,
“index”: 0
}
]
]
},
“Function1”: {
“main”: [
[
{
“node”: “Set prams email2”,
“type”: “main”,
“index”: 0
}
]
]
},
“If1”: {
“main”: [
[
{
“node”: “Function2”,
“type”: “main”,
“index”: 0
}
]
]
},
“Schedule Trigger”: {
“main”: [
[
{
“node”: “Radium Authentication”,
“type”: “main”,
“index”: 0
}
]
]
}
},
“pinData”: {},
“meta”: {
“instanceId”: “56618ac74fc1653fa13a004decd583a5fc421e12ac60e59c391259dc6bd565ce”
}
}>

Describe the problem/error/question

What is the error message (if any)?

Please share your workflow

(Select the nodes on your canvas and use the keyboard shortcuts CMD+C/CTRL+C and CMD+V/CTRL+V to copy and paste the workflow.)

Share the output returned by the last node

Information on your n8n setup

  • n8n version:
  • Database (default: SQLite):
  • n8n EXECUTIONS_PROCESS setting (default: own, main):
  • Running n8n via (Docker, npm, n8n cloud, desktop app):
  • Operating system:

I can see the issue in your workflow - you have Function1 and Function2 for deduplication, but Function2 is only filtering tickets, not marking them as sent. This is why you’re getting duplicate emails on every workflow run.

The Problem

Your current flow:

  1. Function1 filters out already-sent tickets ✓
  2. Emails are sent ✓
  3. If1 checks for success ✓
  4. Function2 filters again ✗ (but never updates the tracking)

The staticData.sentTickets object is checked but never updated, so the same tickets pass through every run.

The Fix

Replace your Function2 node code with this:

const staticData = this.getWorkflowStaticData('global');

if (!staticData.sentTickets) {
  staticData.sentTickets = {};
}

const now = Date.now();

// Mark all successfully sent tickets
for (const item of items) {
  const id = String(item.json.dedup_id || '').trim();
  if (id) {
    staticData.sentTickets[id] = now;
  }
}

// Optional: Clean up entries older than 7 days
const TTL = 7 * 24 * 60 * 60 * 1000;
for (const [ticketId, timestamp] of Object.entries(staticData.sentTickets)) {
  if (typeof timestamp === 'number' && (now - timestamp > TTL)) {
    delete staticData.sentTickets[ticketId];
  }
}

return items;

What This Does

  1. Records each sent ticket with a timestamp in workflow static data (persists across executions)
  2. Automatically purges old entries after 7 days to prevent memory bloat
  3. Returns all items so you can track the results downstream

Workflow Flow Summary

  • Function1: Filters OUT tickets already in staticData.sentTickets (prevents sending)
  • Loop + Email: Sends emails one by one
  • If1: Checks email was successful
  • Function2: Records the ticket ID as sent (prevents future duplicates)

This pattern is exactly how I build alert deduplication for clients in production ITSM workflows. The key is separating the “check” (Function1) from the “record” (Function2) so you only mark tickets as sent after successful email delivery.

Let me know if you need help testing this or want to add additional features like retry logic for failed emails!

I tried this code in “function2” node, its still sending duplicate mails {
“nodes”: [
{
“parameters”: {
“requestMethod”: “POST”,
“url”: “={{ $node[“Radium Authentication”].json.url }}/itsm/get_all_tickets”,
“allowUnauthorizedCerts”: true,
“options”: {},
“bodyParametersUi”: {
“parameter”: [
{}
]
},
“headerParametersUi”: {
“parameter”: [
{
“name”: “Authorization”,
“value”: “={{ $node[“Radium Authentication”].json.token }}”
}
]
},
“queryParametersUi”: {
“parameter”: [
{
“name”: “page”,
“value”: “1”
},
{
“name”: “offset”,
“value”: “100”
}
]
}
},
“id”: “662d916b-7edd-4a5f-9337-83b4c81b650b”,
“name”: “Get Credential list Request”,
“type”: “n8n-nodes-base.httpRequest”,
“typeVersion”: 2,
“position”: [
-1960,
280
]
},
{
“parameters”: {},
“id”: “bc263f6e-4b85-4544-9372-e9070205ee7f”,
“name”: “Radium Authentication”,
“type”: “n8n-nodes-base.RadiumDetails”,
“typeVersion”: 1,
“position”: [
-2180,
280
]
},
{
“parameters”: {
“jsCode”: “\n/**\n * Filters tickets that are:\n * - status NEW (case-insensitive),\n * - created within the last 24 hours,\n * - at least 30 minutes old.\n *\n * It parses created_on1 robustly and builds an ISO string with +05:30 (IST),\n * so comparisons are done on UTC epoch milliseconds reliably.\n /\n\nconst IST_TZ = ‘+05:30’;\nconst NOW_MS = Date.now();\nconst TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1000;\nconst THIRTY_MINUTES_MS = 30 * 60 * 1000;\nconst WINDOW_START_MS = NOW_MS - TWENTY_FOUR_HOURS_MS;\n\n// ---- Collect tickets from typical shapes across all incoming items ----\nfunction collectTickets(items) {\n const all = [];\n for (const it of items) {\n const root = it.json;\n\n if (Array.isArray(root)) {\n all.push(…root);\n continue;\n }\n if (Array.isArray(root?.response)) {\n all.push(…root.response);\n continue;\n }\n if (Array.isArray(root?.response?.response)) {\n all.push(…root.response.response);\n continue;\n }\n\n // Fallback: single ticket per item\n if (root && typeof root === ‘object’) {\n all.push(root);\n }\n }\n return all;\n}\n\n// ---- Parse “created_on1” to UTC epoch ms by building an ISO with IST offset ----\n// Supports: “MM/DD/YYYY, HH:MM:SS” or “DD/MM/YYYY, HH:MM:SS”, optional “AM/PM”\n// Also supports “YYYY-MM-DD HH:MM:SS” and ISO-like strings.\nfunction parseIstToEpochMs(str) {\n if (typeof str !== ‘string’) return null;\n let s = str.trim();\n\n // If it’s already an ISO string, let Date.parse handle it.\n // e.g., “2025-12-24T10:30:00+05:30” or “2025-12-24 10:30:00”\n if (/^\d{4}-\d{2}-\d{2}/.test(s)) {\n // If no timezone is present, assume IST\n if (!/[zZ]|[+\-]\d{2}:\d{2}$/.test(s)) s = s.replace(’ ‘, ‘T’) + IST_TZ;\n const ms = Date.parse(s);\n return Number.isNaN(ms) ? null : ms;\n }\n\n // Split “date, time” OR “date time”\n let datePart, timePart;\n const byComma = s.split(’, ‘);\n if (byComma.length >= 2) {\n datePart = byComma[0];\n timePart = byComma.slice(1).join(’ ').trim();\n } else {\n // Try space split between date and time\n const m = s.match(/^(.+?)\s+(\d{1,2}:\d{2}:\d{2}(?:\s[AP]M)?)$/i);\n if (!m) return null;\n datePart = m[1];\n timePart = m[2];\n }\n\n // Determine separator and split date\n const sep = datePart.includes(‘/’) ? ‘/’ :\n datePart.includes(‘-’) ? ‘-’ :\n datePart.includes(‘.’) ? ‘.’ : ‘/’;\n const parts = datePart.split(sep).map(x => x.trim());\n\n let year, month, day;\n if (parts[0].length === 4) {\n // YYYY-MM-DD\n year = Number(parts[0]);\n month = Number(parts[1]);\n day = Number(parts[2]);\n } else {\n // Decide MM/DD/YYYY vs DD/MM/YYYY by first part value\n const a = parts.map(Number);\n if (a[0] > 12) { // DD/MM/YYYY\n day = a[0]; month = a[1]; year = a[2];\n } else { // MM/DD/YYYY\n month = a[0]; day = a[1]; year = a[2];\n }\n }\n\n // Parse time with optional AM/PM\n let tp = timePart.trim();\n let ampm = null;\n if (/AM$/i.test(tp)) { ampm = ‘AM’; tp = tp.replace(/AM$/i, ‘’).trim(); }\n else if (/PM$/i.test(tp)) { ampm = ‘PM’; tp = tp.replace(/PM$/i, ‘’).trim(); }\n\n const [hStr, mStr, sStr] = tp.split(‘:’);\n let hour = Number(hStr);\n const minute = Number(mStr);\n const second = Number(sStr);\n\n if ([year, month, day, hour, minute, second].some(n => Number.isNaN(n))) return null;\n\n // 12h → 24h\n if (ampm === ‘AM’ && hour === 12) hour = 0;\n if (ampm === ‘PM’ && hour !== 12) hour += 12;\n\n // Build ISO string with IST timezone\n const iso =\n ${String(year).padStart(4, '0')}- +\n ${String(month).padStart(2, '0')}- +\n ${String(day).padStart(2, '0')}T +\n ${String(hour).padStart(2, '0')}: +\n ${String(minute).padStart(2, '0')}: +\n ${String(second).padStart(2, '0')} +\n ${IST_TZ};\n\n const ms = Date.parse(iso);\n return Number.isNaN(ms) ? null : ms;\n}\n\n// ---- Main filter ----\nconst tickets = collectTickets(items);\nconst result = ;\n\nfor (const t of tickets) {\n const created =\n t?.created_on1 ?? t?.created_on ?? t?.CreatedOn ?? t?.createdAt;\n const statusRaw =\n t?.radium_incident_status1 ?? t?.radium_incident_status ??\n t?.status ?? t?.incident_status;\n\n if (!created || !statusRaw) continue;\n\n const status = String(statusRaw).trim().toLowerCase();\n if (status !== ‘new’) continue;\n\n const createdMs = parseIstToEpochMs(String(created));\n if (createdMs == null) continue;\n\n // Only last 24h (inclusive) and not future\n if (createdMs < WINDOW_START_MS || createdMs > NOW_MS) continue;\n\n const ageMs = NOW_MS - createdMs;\n if (ageMs < THIRTY_MINUTES_MS) continue;\n\n result.push({\n json: {\n radium_incident_id1: t.radium_incident_id1 ?? t.id ?? t.ticket_id,\n age_minutes: Math.floor(ageMs / 60000),\n status: statusRaw,\n created_on: created,\n },\n });\n}\n\nreturn result;\n”
},
“type”: “n8n-nodes-base.code”,
“typeVersion”: 2,
“position”: [
-1760,
280
],
“id”: “47b47c70-1912-4756-bf4c-d81eb6fd545d”,
“name”: “Code”,
“alwaysOutputData”: false
},
{
“parameters”: {
“conditions”: {
“options”: {
“caseSensitive”: true,
“leftValue”: “”,
“typeValidation”: “strict”,
“version”: 2
},
“conditions”: [
{
“id”: “69f637ba-02e3-4d92-a306-e67059a183f5”,
“leftValue”: “={{ $json.status }}”,
“rightValue”: “new”,
“operator”: {
“type”: “string”,
“operation”: “equals”,
“name”: “filter.operator.equals”
}
},
{
“id”: “931bb843-f4fb-4a6e-94d0-ab52e3ff271d”,
“leftValue”: “={{ $json.age_minutes }}”,
“rightValue”: 30,
“operator”: {
“type”: “number”,
“operation”: “gte”
}
}
],
“combinator”: “and”
},
“options”: {}
},
“type”: “n8n-nodes-base.if”,
“typeVersion”: 2.2,
“position”: [
-1560,
280
],
“id”: “ab567324-a52c-46b7-8f93-e59d4bea5ac7”,
“name”: “If”
},
{
“parameters”: {
“jsCode”: “return items.map(item => {\n const id = String(\n item.json.radium_incident_id1 ??\n item.json.Radium_ticket_id ??\n item.json.ticket_id ??\n item.json.id\n ).trim();\n\n return {\n json: {\n Radium_Ticket: ‘ITSM’,\n Radium_ticket_id: id,\n email_type: ‘ITSM_NEW_TICKET_ALERT’,\n ticket_id: item.json.ticket_id ?? id,\n created_on: item.json.created_on,\n age_minutes: item.json.age_minutes,\n dedup_id: id\n }\n };\n});\n”
},
“type”: “n8n-nodes-base.code”,
“typeVersion”: 2,
“position”: [
-1300,
260
],
“id”: “143f536b-f443-4a0a-acc6-7f246e15bc7d”,
“name”: “Code1”
},
{
“parameters”: {
“options”: {
“reset”: false
}
},
“type”: “n8n-nodes-base.splitInBatches”,
“typeVersion”: 3,
“position”: [
-660,
100
],
“id”: “0685fbd5-9a33-49fa-aaf4-6494feee0a3b”,
“name”: “Loop Over Items”
},
{
“parameters”: {
“requestMethod”: “POST”,
“url”: “={{$node[“Radium Authentication”].json[“url”]}}/send_mail”,
“allowUnauthorizedCerts”: true,
“options”: {},
“bodyParametersUi”: {
“parameter”: [
{
“name”: “toList”,
“value”: “={{ $json.to }}”
},
{
“name”: “subject”,
“value”: “={{ $json.subject }}”
},
{
“name”: “email_type”,
“value”: “={{ $json[“emailType”] }}”
},
{
“name”: “message”,
“value”: “={{ $json.body }}”
},
{
“name”: “email_server_id”,
“value”: “1”
}
]
},
“headerParametersUi”: {
“parameter”: [
{
“name”: “Authorization”,
“value”: “={{$node[“Radium Authentication”].json[“token”]}}”
}
]
}
},
“id”: “edd21cbb-99b7-414b-930f-cbae8794acc4”,
“name”: “Sending Email2”,
“type”: “n8n-nodes-base.httpRequest”,
“typeVersion”: 2,
“position”: [
-420,
100
],
“alwaysOutputData”: true,
“continueOnFail”: true
},
{
“parameters”: {
“values”: {
“string”: [
{
“name”: “subject”,
“value”: “ITSM Ticket Alert”
},
{
“name”: “emailType”,
“value”: “={{ $json.email_type }}”
},
{
“name”: “to”,
“value”: “[email protected]
},
{
“name”: “=body”,
“value”: “=\nHello Team,\nThis is an automated notification to inform you that a support ticket has been open for more than 30 minutes.\n\nTicket Details:\n• Ticket ID: {{$json[“Radium_ticket_id”]}}\n• Opened On: {{$json[“created_on”]}}\n• Open Duration: {{$json[“age_minutes”]}} minutes\n\nKindly review the ticket at the earliest and take necessary action.\n\nRegards,\nITSM Automation System\nNote: This is a system-generated email. Please do not reply.\n”
},
{
“name”: “dedup_id”,
“value”: “=\n={{ \n String(\n $json.radium_incident_id1 ??\n $json.Radium_ticket_id ??\n $json.id ??\n $json.ticket_id\n ).trim()\n}}\n”
}
]
},
“options”: {}
},
“id”: “7cfc4886-0346-47a3-aed2-22558d942255”,
“name”: “Set prams email2”,
“type”: “n8n-nodes-base.set”,
“typeVersion”: 1,
“position”: [
-820,
260
]
},
{
“parameters”: {
“functionCode”: “const staticData = this.getWorkflowStaticData(‘global’);\n\nif (!staticData.sentTickets) {\n staticData.sentTickets = {};\n}\n\nconst now = Date.now();\n\n// Mark all successfully sent tickets\nfor (const item of items) {\n const id = String(item.json.dedup_id || ‘’).trim();\n if (id) {\n staticData.sentTickets[id] = now;\n }\n}\n\n// Optional: Clean up entries older than 7 days\nconst TTL = 7 * 24 * 60 * 60 * 1000;\nfor (const [ticketId, timestamp] of Object.entries(staticData.sentTickets)) {\n if (typeof timestamp === ‘number’ && (now - timestamp > TTL)) {\n delete staticData.sentTickets[ticketId];\n }\n}\n\nreturn items;”
},
“id”: “7622f2fc-0efe-43aa-a4a4-798dcd4a945e”,
“name”: “Function2”,
“type”: “n8n-nodes-base.function”,
“typeVersion”: 1,
“position”: [
40,
240
]
},
{
“parameters”: {
“functionCode”: “const staticData = this.getWorkflowStaticData(‘global’);\n\nif (!staticData.sentTickets) {\n staticData.sentTickets = {};\n}\n\nreturn items.filter(item => {\n const dedupId = String(item.json.dedup_id).trim();\n return !staticData.sentTickets[dedupId];\n});”
},
“id”: “677f505e-a7b5-4d5b-9bbe-21e54e08bc68”,
“name”: “Function1”,
“type”: “n8n-nodes-base.function”,
“typeVersion”: 1,
“position”: [
-1040,
260
]
},
{
“parameters”: {
“conditions”: {
“options”: {
“caseSensitive”: true,
“leftValue”: “”,
“typeValidation”: “strict”,
“version”: 2
},
“conditions”: [
{
“id”: “20898851-bc34-40b7-abb0-913c82e34138”,
“leftValue”: “={{ $json.status }}”,
“rightValue”: “success”,
“operator”: {
“type”: “string”,
“operation”: “equals”
}
}
],
“combinator”: “and”
},
“options”: {}
},
“type”: “n8n-nodes-base.if”,
“typeVersion”: 2.2,
“position”: [
0,
0
],
“id”: “7a7e80f6-e884-4c02-8fd5-c83f6b246dbb”,
“name”: “If1”
},
{
“parameters”: {
“rule”: {
“interval”: [
{
“field”: “minutes”
}
]
}
},
“type”: “n8n-nodes-base.scheduleTrigger”,
“typeVersion”: 1.2,
“position”: [
-2440,
280
],
“id”: “99fcb4ab-0d7e-4e75-bda5-d34bbca51e39”,
“name”: “Schedule Trigger”
}
],
“connections”: {
“Get Credential list Request”: {
“main”: [
[
{
“node”: “Code”,
“type”: “main”,
“index”: 0
}
]
]
},
“Radium Authentication”: {
“main”: [
[
{
“node”: “Get Credential list Request”,
“type”: “main”,
“index”: 0
}
]
]
},
“Code”: {
“main”: [
[
{
“node”: “If”,
“type”: “main”,
“index”: 0
}
]
]
},
“If”: {
“main”: [
[
{
“node”: “Code1”,
“type”: “main”,
“index”: 0
}
]
]
},
“Code1”: {
“main”: [
[
{
“node”: “Function1”,
“type”: “main”,
“index”: 0
}
]
]
},
“Loop Over Items”: {
“main”: [
,
[
{
“node”: “Sending Email2”,
“type”: “main”,
“index”: 0
}
]
]
},
“Sending Email2”: {
“main”: [
[
{
“node”: “If1”,
“type”: “main”,
“index”: 0
},
{
“node”: “Loop Over Items”,
“type”: “main”,
“index”: 0
}
]
]
},
“Set prams email2”: {
“main”: [
[
{
“node”: “Loop Over Items”,
“type”: “main”,
“index”: 0
}
]
]
},
“Function1”: {
“main”: [
[
{
“node”: “Set prams email2”,
“type”: “main”,
“index”: 0
}
]
]
},
“If1”: {
“main”: [
[
{
“node”: “Function2”,
“type”: “main”,
“index”: 0
}
]
]
},
“Schedule Trigger”: {
“main”: [
[
{
“node”: “Radium Authentication”,
“type”: “main”,
“index”: 0
}
]
]
}
},
“pinData”: {},
“meta”: {
“instanceId”: “56618ac74fc1653fa13a004decd583a5fc421e12ac60e59c391259dc6bd565ce”
}
}

Hey @Bhagyashri_Barot !

Can you please share your json like this?

When I am trying to copy and paste is not working (I think the markup is broken if you don’t share like this:

glxgzrh0

Cheers!

Hi @Bhagyashri_Barot

If you attach your workflow correctly, anyone will be able to suggest a suitable solution,

For now, you are probably looking for the Remove Duplicates node:

1 Like
{
  "nodes": [
    {
      "parameters": {
        "requestMethod": "POST",
        "url": "={{ $node[\"Radium Authentication\"].json.url }}/itsm/get_all_tickets",
        "allowUnauthorizedCerts": true,
        "options": {},
        "bodyParametersUi": {
          "parameter": [
            {}
          ]
        },
        "headerParametersUi": {
          "parameter": [
            {
              "name": "Authorization",
              "value": "={{ $node[\"Radium Authentication\"].json.token }}"
            }
          ]
        },
        "queryParametersUi": {
          "parameter": [
            {
              "name": "page",
              "value": "1"
            },
            {
              "name": "offset",
              "value": "100"
            }
          ]
        }
      },
      "id": "aa23f2c9-8b47-4048-8413-451e01ef2bdb",
      "name": "Get Credential list Request",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 2,
      "position": [
        -1960,
        280
      ]
    },
    {
      "parameters": {},
      "id": "35ee1483-2b63-444b-866e-e706b789e97e",
      "name": "Radium Authentication",
      "type": "n8n-nodes-base.RadiumDetails",
      "typeVersion": 1,
      "position": [
        -2180,
        280
      ]
    },
    {
      "parameters": {
        "jsCode": "\n/**\n * Filters tickets that are:\n *  - status NEW (case-insensitive),\n *  - created within the last 24 hours,\n *  - at least 30 minutes old.\n *\n * It parses created_on1 robustly and builds an ISO string with +05:30 (IST),\n * so comparisons are done on UTC epoch milliseconds reliably.\n */\n\nconst IST_TZ = '+05:30';\nconst NOW_MS = Date.now();\nconst TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1000;\nconst THIRTY_MINUTES_MS = 30 * 60 * 1000;\nconst WINDOW_START_MS = NOW_MS - TWENTY_FOUR_HOURS_MS;\n\n// ---- Collect tickets from typical shapes across all incoming items ----\nfunction collectTickets(items) {\n  const all = [];\n  for (const it of items) {\n    const root = it.json;\n\n    if (Array.isArray(root)) {\n      all.push(...root);\n      continue;\n    }\n    if (Array.isArray(root?.response)) {\n      all.push(...root.response);\n      continue;\n    }\n    if (Array.isArray(root?.response?.response)) {\n      all.push(...root.response.response);\n      continue;\n    }\n\n    // Fallback: single ticket per item\n    if (root && typeof root === 'object') {\n      all.push(root);\n    }\n  }\n  return all;\n}\n\n// ---- Parse \"created_on1\" to UTC epoch ms by building an ISO with IST offset ----\n// Supports: \"MM/DD/YYYY, HH:MM:SS\" or \"DD/MM/YYYY, HH:MM:SS\", optional \"AM/PM\"\n// Also supports \"YYYY-MM-DD HH:MM:SS\" and ISO-like strings.\nfunction parseIstToEpochMs(str) {\n  if (typeof str !== 'string') return null;\n  let s = str.trim();\n\n  // If it's already an ISO string, let Date.parse handle it.\n  // e.g., \"2025-12-24T10:30:00+05:30\" or \"2025-12-24 10:30:00\"\n  if (/^\\d{4}-\\d{2}-\\d{2}/.test(s)) {\n    // If no timezone is present, assume IST\n    if (!/[zZ]|[+\\-]\\d{2}:\\d{2}$/.test(s)) s = s.replace(' ', 'T') + IST_TZ;\n    const ms = Date.parse(s);\n    return Number.isNaN(ms) ? null : ms;\n  }\n\n  // Split \"date, time\" OR \"date time\"\n  let datePart, timePart;\n  const byComma = s.split(', ');\n  if (byComma.length >= 2) {\n    datePart = byComma[0];\n    timePart = byComma.slice(1).join(' ').trim();\n  } else {\n    // Try space split between date and time\n    const m = s.match(/^(.+?)\\s+(\\d{1,2}:\\d{2}:\\d{2}(?:\\s*[AP]M)?)$/i);\n    if (!m) return null;\n    datePart = m[1];\n    timePart = m[2];\n  }\n\n  // Determine separator and split date\n  const sep = datePart.includes('/') ? '/' :\n              datePart.includes('-') ? '-' :\n              datePart.includes('.') ? '.' : '/';\n  const parts = datePart.split(sep).map(x => x.trim());\n\n  let year, month, day;\n  if (parts[0].length === 4) {\n    // YYYY-MM-DD\n    year = Number(parts[0]);\n    month = Number(parts[1]);\n    day = Number(parts[2]);\n  } else {\n    // Decide MM/DD/YYYY vs DD/MM/YYYY by first part value\n    const a = parts.map(Number);\n    if (a[0] > 12) { // DD/MM/YYYY\n      day = a[0]; month = a[1]; year = a[2];\n    } else { // MM/DD/YYYY\n      month = a[0]; day = a[1]; year = a[2];\n    }\n  }\n\n  // Parse time with optional AM/PM\n  let tp = timePart.trim();\n  let ampm = null;\n  if (/AM$/i.test(tp)) { ampm = 'AM'; tp = tp.replace(/AM$/i, '').trim(); }\n  else if (/PM$/i.test(tp)) { ampm = 'PM'; tp = tp.replace(/PM$/i, '').trim(); }\n\n  const [hStr, mStr, sStr] = tp.split(':');\n  let hour = Number(hStr);\n  const minute = Number(mStr);\n  const second = Number(sStr);\n\n  if ([year, month, day, hour, minute, second].some(n => Number.isNaN(n))) return null;\n\n  // 12h -> 24h\n  if (ampm === 'AM' && hour === 12) hour = 0;\n  if (ampm === 'PM' && hour !== 12) hour += 12;\n\n  // Build ISO string with IST timezone\n  const iso =\n    `${String(year).padStart(4, '0')}-` +\n    `${String(month).padStart(2, '0')}-` +\n    `${String(day).padStart(2, '0')}T` +\n    `${String(hour).padStart(2, '0')}:` +\n    `${String(minute).padStart(2, '0')}:` +\n    `${String(second).padStart(2, '0')}` +\n    `${IST_TZ}`;\n\n  const ms = Date.parse(iso);\n  return Number.isNaN(ms) ? null : ms;\n}\n\n// ---- Main filter ----\nconst tickets = collectTickets(items);\nconst result = [];\n\nfor (const t of tickets) {\n  const created =\n    t?.created_on1 ?? t?.created_on ?? t?.CreatedOn ?? t?.createdAt;\n  const statusRaw =\n    t?.radium_incident_status1 ?? t?.radium_incident_status ??\n    t?.status ?? t?.incident_status;\n\n  if (!created || !statusRaw) continue;\n\n  const status = String(statusRaw).trim().toLowerCase();\n  if (status !== 'new') continue;\n\n  const createdMs = parseIstToEpochMs(String(created));\n  if (createdMs == null) continue;\n\n  // Only last 24h (inclusive) and not future\n  if (createdMs < WINDOW_START_MS || createdMs > NOW_MS) continue;\n\n  const ageMs = NOW_MS - createdMs;\n  if (ageMs < THIRTY_MINUTES_MS) continue;\n\n  result.push({\n    json: {\n      radium_incident_id1: t.radium_incident_id1 ?? t.id ?? t.ticket_id,\n      age_minutes: Math.floor(ageMs / 60000),\n      status: statusRaw,\n      created_on: created,\n    },\n  });\n}\n\nreturn result;\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1760,
        280
      ],
      "id": "30900af6-db23-483e-8752-839bfecb5d96",
      "name": "Code",
      "alwaysOutputData": false
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "69f637ba-02e3-4d92-a306-e67059a183f5",
              "leftValue": "={{ $json.status }}",
              "rightValue": "new",
              "operator": {
                "type": "string",
                "operation": "equals",
                "name": "filter.operator.equals"
              }
            },
            {
              "id": "931bb843-f4fb-4a6e-94d0-ab52e3ff271d",
              "leftValue": "={{ $json.age_minutes }}",
              "rightValue": 30,
              "operator": {
                "type": "number",
                "operation": "gte"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        -1560,
        280
      ],
      "id": "f42c304c-0931-44ff-94cb-1df11640e425",
      "name": "If"
    },
    {
      "parameters": {
        "jsCode": "return items.map(item => {\n  const id = String(\n    item.json.radium_incident_id1 ??\n    item.json.Radium_ticket_id ??\n    item.json.ticket_id ??\n    item.json.id\n  ).trim();\n\n  return {\n    json: {\n      Radium_Ticket: 'ITSM',\n      Radium_ticket_id: id,\n      email_type: 'ITSM_NEW_TICKET_ALERT',\n      ticket_id: item.json.ticket_id ?? id,\n      created_on: item.json.created_on,\n      age_minutes: item.json.age_minutes,\n      dedup_id: id\n    }\n  };\n});\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1300,
        260
      ],
      "id": "03431376-d9de-40ba-b9cc-0ff7288c8349",
      "name": "Code1"
    },
    {
      "parameters": {
        "options": {
          "reset": false
        }
      },
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [
        -660,
        100
      ],
      "id": "e329e641-d29b-4fb7-a080-53296a731aef",
      "name": "Loop Over Items"
    },
    {
      "parameters": {
        "requestMethod": "POST",
        "url": "={{$node[\"Radium Authentication\"].json[\"url\"]}}/send_mail",
        "allowUnauthorizedCerts": true,
        "options": {},
        "bodyParametersUi": {
          "parameter": [
            {
              "name": "toList",
              "value": "={{ $json.to }}"
            },
            {
              "name": "subject",
              "value": "={{ $json.subject }}"
            },
            {
              "name": "email_type",
              "value": "={{ $json[\"emailType\"] }}"
            },
            {
              "name": "message",
              "value": "={{ $json.body }}"
            },
            {
              "name": "email_server_id",
              "value": "1"
            }
          ]
        },
        "headerParametersUi": {
          "parameter": [
            {
              "name": "Authorization",
              "value": "={{$node[\"Radium Authentication\"].json[\"token\"]}}"
            }
          ]
        }
      },
      "id": "3a7316ca-0ab7-4b8f-9b11-30e8c972855b",
      "name": "Sending Email2",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 2,
      "position": [
        -420,
        100
      ],
      "alwaysOutputData": true,
      "continueOnFail": true
    },
    {
      "parameters": {
        "values": {
          "string": [
            {
              "name": "subject",
              "value": "ITSM Ticket Alert"
            },
            {
              "name": "emailType",
              "value": "={{ $json.email_type }}"
            },
            {
              "name": "to",
              "value": "[email protected]"
            },
            {
              "name": "=body",
              "value": "=\nHello Team,<br><br>\nThis is an automated notification to inform you that a support ticket has been open for more than 30 minutes.<br><br>\n\n<b>Ticket Details:</b><br>\n• <b>Ticket ID:</b> {{$json[\"Radium_ticket_id\"]}}<br>\n• <b>Opened On:</b> {{$json[\"created_on\"]}}<br>\n• <b>Open Duration:</b> {{$json[\"age_minutes\"]}} minutes<br><br>\n\nKindly review the ticket at the earliest and take necessary action.<br><br>\n\nRegards,<br>\nITSM Automation System<br><br>\n<i>Note: This is a system-generated email. Please do not reply.</i>\n"
            },
            {
              "name": "dedup_id",
              "value": "=\n={{ \n  String(\n    $json.radium_incident_id1 ??\n    $json.Radium_ticket_id ??\n    $json.id ??\n    $json.ticket_id\n  ).trim()\n}}\n"
            }
          ]
        },
        "options": {}
      },
      "id": "ea0c031c-6afa-49d8-b621-ba87907b7bd2",
      "name": "Set prams email2",
      "type": "n8n-nodes-base.set",
      "typeVersion": 1,
      "position": [
        -820,
        260
      ]
    },
    {
      "parameters": {
        "functionCode": "const staticData = this.getWorkflowStaticData('global');\n\nif (!staticData.sentTickets) {\n  staticData.sentTickets = {};\n}\n\nconst now = Date.now();\nconst TTL = 7 * 24 * 60 * 60 * 1000; // 7 days\n\nfor (const [id, ts] of Object.entries(staticData.sentTickets)) {\n  if (typeof ts === 'number' && (now - ts > TTL)) {\n    delete staticData.sentTickets[id];\n  }\n}\n\nreturn items;\n"
      },
      "id": "91f99553-dd1e-4ad6-9f0a-213ca7e923ce",
      "name": "Function2",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        40,
        240
      ]
    },
    {
      "parameters": {
        "functionCode": "const staticData = this.getWorkflowStaticData('global');\n\nif (!staticData.sentTickets) {\n  staticData.sentTickets = {};\n}\n\nconst now = Date.now();\n\nreturn items.filter(item => {\n  const dedupId = String(item.json.dedup_id).trim();\n  if (!dedupId) return false;\n\n  // If already reserved/sent → block\n  if (staticData.sentTickets[dedupId]) {\n    return false;\n  }\n\n  // Reserve immediately (IMPORTANT)\n  staticData.sentTickets[dedupId] = now;\n  return true;\n});\n"
      },
      "id": "056b0a8a-61c7-4905-a8f9-5cee9bcab48f",
      "name": "Function1",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        -1040,
        260
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "20898851-bc34-40b7-abb0-913c82e34138",
              "leftValue": "={{ $json.status }}",
              "rightValue": "success",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        0,
        0
      ],
      "id": "dd016ccc-5447-4404-8cef-3c71fcde44e3",
      "name": "If1"
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -2440,
        280
      ],
      "id": "f02f1f7d-c895-4e29-ab65-c01c4e1954ab",
      "name": "Schedule Trigger"
    }
  ],
  "connections": {
    "Get Credential list Request": {
      "main": [
        [
          {
            "node": "Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Radium Authentication": {
      "main": [
        [
          {
            "node": "Get Credential list Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code": {
      "main": [
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If": {
      "main": [
        [
          {
            "node": "Code1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code1": {
      "main": [
        [
          {
            "node": "Function1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items": {
      "main": [
        [],
        [
          {
            "node": "Sending Email2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sending Email2": {
      "main": [
        [
          {
            "node": "If1",
            "type": "main",
            "index": 0
          },
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set prams email2": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Function1": {
      "main": [
        [
          {
            "node": "Set prams email2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If1": {
      "main": [
        [
          {
            "node": "Function2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Radium Authentication",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "meta": {
    "instanceId": "56618ac74fc1653fa13a004decd583a5fc421e12ac60e59c391259dc6bd565ce"
  }
}
```      Thanks for the support now this code started working i made some changes

Alternative Solution: Use the Built-in “Remove Duplicates” Node

While the Function node approach I shared earlier works great, n8n actually has a built-in “Remove Duplicates” node that makes this even simpler and more maintainable!

Simpler Workflow Pattern:

Instead of using Function nodes with staticData, you can restructure your workflow like this:

Schedule Trigger → Get Tickets → Filter (NEW status, >30min) 
→ Remove Duplicates (by ticket ID) → Send Email

How to Use Remove Duplicates Node:

  1. Add the “Remove Duplicates” node after your filtering logic

  2. Configure it:

    • Compare: Select “Selected Fields”
    • Fields to Compare: Choose the field with your ticket ID (e.g., radium_incident_id1)
    • Output: Keep only unique items
  3. This node automatically:

    • Removes duplicate tickets within the same execution
    • Keeps only the first occurrence of each unique ticket ID
    • No need to manage staticData manually

Benefits of This Approach:

:white_check_mark: Simpler - No custom JavaScript code needed
:white_check_mark: Visual - Easy to understand in the workflow canvas
:white_check_mark: Maintainable - Less code to debug
:white_check_mark: Built-in - Native n8n functionality

When to Use Each Approach:

Use Remove Duplicates Node when:

  • You want to deduplicate tickets within the current execution
  • You’re okay with checking the same tickets on each run
  • Simpler implementation is preferred

Use Function node with staticData when:

  • You need to track tickets across multiple executions (like my original solution)
  • You want to permanently remember “already sent” tickets
  • You need to prevent sending emails to the same ticket ID even days/weeks later

For your ITSM ticket alert use case, the Function node with staticData approach I shared earlier is actually better because it prevents duplicate emails across all workflow runs (not just within one run).

But if you prefer a simpler approach and don’t mind the workflow checking tickets each time, the Remove Duplicates node is a great alternative!

Let me know which approach works better for your needs!

1 Like