AutomationFlowsGeneral › RSS to X, LinkedIn & Discord

RSS to X, LinkedIn & Discord

Original n8n title: RSS to Multi-channel Social (x / Linkedin / Discord)

RSS to Multi-Channel Social (X / LinkedIn / Discord). Uses stickyNote, scheduleTrigger, httpRequest. Scheduled trigger; 19 nodes.

Cron / scheduled trigger★★★★☆ complexity19 nodesHTTP Request
General Trigger: Cron / scheduled Nodes: 19 Complexity: ★★★★☆ Added:

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "name": "RSS to Multi-Channel Social (X / LinkedIn / Discord)",
  "nodes": [
    {
      "parameters": {
        "content": "## RSS to Multi-Channel Social\n\nSchedule trigger fetches RSS feeds, dedups against a 7-day in-memory window of seen guids, formats per-channel posts (X 280 chars, LinkedIn longer, Discord embed), and posts via the enabled channels in parallel.\n\n**Production patterns wired:**\n- Rate limit (opt-in, `RATE_LIMIT_ENABLED=1`, per-feed-host)\n- Idempotency on `item.guid` (opt-in, `IDEMPOTENCY_ENABLED=1`, 7-day window)\n- Error branches per channel (always on, partial-failure tolerant)\n\nNo HMAC because the trigger is internal cron, not a public webhook.\n\nSee `README.md` for setup, env vars, and extension recipes.",
        "height": 320,
        "width": 400,
        "color": 6
      },
      "id": "note-intro",
      "name": "Sticky Note - Intro",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -200,
        -100
      ]
    },
    {
      "parameters": {
        "content": "### >> SET ME <<\n\n1. Set `RSS_FEEDS` to a comma-separated list of feed URLs.\n2. Set `SOCIAL_CHANNELS` to a comma-separated subset of `x,linkedin,discord`.\n3. Add credentials: X OAuth 1.0a (`twitterOAuth1Api`), LinkedIn OAuth 2.0 (`linkedInOAuth2Api`).\n4. Set `DISCORD_WEBHOOK_URL` env var (no credential needed).\n5. Set `SLACK_OPS_WEBHOOK` for the ops summary and error alerts.\n6. Self-hosted n8n: set `NODE_FUNCTION_ALLOW_BUILTIN=crypto`.\n7. Adjust schedule in the Schedule Trigger node (default every 30 min).",
        "height": 320,
        "width": 380,
        "color": 5
      },
      "id": "note-setup",
      "name": "Sticky Note - Setup",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -200,
        240
      ]
    },
    {
      "parameters": {
        "content": "## Production Patterns\n\nThree patterns wired. Two opt-in nodes plus always-on per-channel error branches.\n\n- **Rate limit:** `RATE_LIMIT_ENABLED=1` (12 fetches / hour / feed-host)\n- **Idempotency:** `IDEMPOTENCY_ENABLED=1` (7-day window on `item.guid`)\n- **Per-channel error branches:** always on. Each channel POST has `onError: continueErrorOutput`. One channel failing does not break the others.\n\nFor clustered n8n, swap the in-memory dedup for Redis SET NX EX 604800. Snippet in the node's comments.",
        "height": 320,
        "width": 380,
        "color": 7
      },
      "id": "note-production-patterns",
      "name": "Sticky Note - Production Patterns",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        840,
        -260
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 30
            }
          ]
        }
      },
      "id": "rss-1-trigger",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        240,
        60
      ]
    },
    {
      "parameters": {
        "jsCode": "// Read RSS_FEEDS env (comma-separated URLs). Emit one item per feed URL\n// so the next HTTP Request fans out.\n\nconst feedsEnv = $env.RSS_FEEDS || '';\nconst feeds = feedsEnv.split(',').map(s => s.trim()).filter(Boolean);\n\nif (feeds.length === 0) {\n  throw new Error('RSS_FEEDS env var is empty. Set it to a comma-separated list of feed URLs.');\n}\n\nreturn feeds.map(url => ({ json: { feedUrl: url } }));"
      },
      "id": "rss-2-list-feeds",
      "name": "List Feeds",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        440,
        60
      ]
    },
    {
      "parameters": {
        "jsCode": "// Per-feed-host sliding-window rate limit, opt-in.\n// 12 fetches per hour per host (i.e. one every 5 min minimum).\n// This blocks an accidentally too-aggressive schedule from hammering a single feed.\n\nif ($env.RATE_LIMIT_ENABLED !== '1') {\n  return $input.all();\n}\n\nconst LIMIT = 12;\nconst WINDOW_MS = 60 * 60 * 1000;\nconst MAX_KEYS = 200;\n\nconst data = $getWorkflowStaticData('global');\ndata.rateBuckets = data.rateBuckets || {};\nconst buckets = data.rateBuckets;\nconst now = Date.now();\n\nfor (const k of Object.keys(buckets)) {\n  buckets[k] = (buckets[k] || []).filter(t => now - t < WINDOW_MS);\n  if (buckets[k].length === 0) delete buckets[k];\n}\nif (Object.keys(buckets).length > MAX_KEYS) {\n  const oldest = Object.entries(buckets).sort((a, b) => (a[1][0] || 0) - (b[1][0] || 0)).slice(0, 50);\n  for (const [k] of oldest) delete buckets[k];\n}\n\nconst out = [];\nfor (const item of $input.all()) {\n  let host = 'unknown';\n  try { host = new URL(item.json.feedUrl).host; } catch (e) { /* keep unknown */ }\n  const hits = buckets[host] || [];\n  if (hits.length >= LIMIT) {\n    // Skip this feed for this cycle. Do not throw because other feeds should keep going.\n    continue;\n  }\n  buckets[host] = [...hits, now];\n  out.push(item);\n}\n\nreturn out;"
      },
      "id": "rss-pp-1-ratelimit",
      "name": "Rate Limit (opt-in)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        640,
        60
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "={{ $json.feedUrl }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "text"
            }
          }
        }
      },
      "id": "rss-3-fetch",
      "name": "Fetch RSS Feed",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        840,
        60
      ],
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "jsCode": "// Lightweight RSS / Atom parser. Extracts items with title / link / guid / pubDate / summary.\n// Avoids a heavyweight XML library by tag-walking; works for typical RSS 2.0 + Atom feeds.\n\nconst out = [];\nfor (const item of $input.all()) {\n  const xml = String(item.json.data || item.json.body || '');\n  const feedUrl = item.json.feedUrl || ($input.first().json.feedUrl) || 'unknown';\n\n  // RSS 2.0: <item>...</item>\n  // Atom: <entry>...</entry>\n  const itemRegex = /<(item|entry)[\\s>][\\s\\S]*?<\\/\\1>/gi;\n  const matches = xml.match(itemRegex) || [];\n\n  for (const block of matches) {\n    const pick = (tag) => {\n      const re = new RegExp('<' + tag + '(?:[^>]*)>([\\\\s\\\\S]*?)<\\\\/' + tag + '>', 'i');\n      const m = block.match(re);\n      return m ? m[1].replace(/<!\\[CDATA\\[|\\]\\]>/g, '').trim() : null;\n    };\n    const linkAttr = (() => {\n      const m = block.match(/<link[^>]*href=\"([^\"]+)\"/i);\n      return m ? m[1] : null;\n    })();\n\n    const title = pick('title') || 'Untitled';\n    const link = pick('link') || linkAttr || null;\n    const guid = pick('guid') || pick('id') || link || (title + ':' + (pick('pubDate') || pick('updated') || ''));\n    const pubDate = pick('pubDate') || pick('updated') || pick('published') || null;\n    const summary = (pick('description') || pick('summary') || pick('content') || '').replace(/<[^>]+>/g, '').trim().slice(0, 500);\n\n    out.push({ json: { feedUrl, title, link, guid, pubDate, summary } });\n  }\n}\n\nreturn out;"
      },
      "id": "rss-4-parse",
      "name": "Parse RSS Items",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1040,
        60
      ]
    },
    {
      "parameters": {
        "jsCode": "// 7-day idempotency window on item guid, opt-in.\n// Filters items already seen.\n\nconst crypto = require('crypto');\n\nif ($env.IDEMPOTENCY_ENABLED !== '1') {\n  return $input.all();\n}\n\nconst WINDOW_MS = 7 * 24 * 60 * 60 * 1000;\nconst MAX_KEYS = 50000;\n\nconst data = $getWorkflowStaticData('global');\ndata.seenKeys = data.seenKeys || {};\nconst seen = data.seenKeys;\nconst now = Date.now();\n\nfor (const k of Object.keys(seen)) {\n  if (now - seen[k] > WINDOW_MS) delete seen[k];\n}\nif (Object.keys(seen).length > MAX_KEYS) {\n  const oldest = Object.entries(seen).sort((a, b) => a[1] - b[1]).slice(0, 5000);\n  for (const [k] of oldest) delete seen[k];\n}\n\nconst out = [];\nfor (const item of $input.all()) {\n  const json = item.json || {};\n  const baseKey = json.guid || json.link || (json.title + ':' + (json.pubDate || ''));\n  const dedupKey = crypto.createHash('sha256').update(String(baseKey)).digest('hex').slice(0, 32);\n  if (seen[dedupKey]) continue;\n  seen[dedupKey] = now;\n  out.push(item);\n}\n\nreturn out;\n\n// Redis variant for clustered n8n:\n// const result = await redis.set('rss-idem:' + dedupKey, '1', 'EX', 604800, 'NX');\n// if (result === null) continue;"
      },
      "id": "rss-pp-2-idempotency",
      "name": "Idempotency Filter (opt-in)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1240,
        60
      ]
    },
    {
      "parameters": {
        "jsCode": "// Normalize each item plus pre-format per-channel post text.\n\nconst out = [];\nfor (const item of $input.all()) {\n  const j = item.json || {};\n  const title = j.title || 'Untitled';\n  const link = j.link || '';\n  const summary = j.summary || '';\n\n  const xPost = (title.length > 240 ? title.slice(0, 237) + '...' : title) + (link ? ' ' + link : '');\n  const linkedinPost = title + (summary ? '\\n\\n' + summary : '') + (link ? '\\n\\n' + link : '');\n  const discordPost = (title + '\\n' + (summary ? summary + '\\n' : '') + (link ? link : '')).slice(0, 1900);\n\n  out.push({\n    json: {\n      ...j,\n      xText: xPost,\n      linkedinText: linkedinPost,\n      discordText: discordPost,\n    },\n  });\n}\nreturn out;"
      },
      "id": "rss-5-normalize",
      "name": "Normalize Items",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1440,
        60
      ]
    },
    {
      "parameters": {
        "jsCode": "// Pick enabled social channels from env. Default: empty (post nowhere).\n// User must explicitly opt in by setting SOCIAL_CHANNELS.\n\nconst chEnv = ($env.SOCIAL_CHANNELS || '').toLowerCase();\nconst enabled = chEnv.split(',').map(s => s.trim()).filter(Boolean);\n\nconst out = [];\nfor (const item of $input.all()) {\n  for (const channel of enabled) {\n    out.push({ json: { ...item.json, channel } });\n  }\n}\nreturn out;"
      },
      "id": "rss-6-fanout",
      "name": "Set Channel Targets",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1640,
        60
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": false,
                  "typeValidation": "loose",
                  "version": 2
                },
                "combinator": "and",
                "conditions": [
                  {
                    "leftValue": "={{ $json.channel }}",
                    "rightValue": "x",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ]
              },
              "outputKey": "x"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": false,
                  "typeValidation": "loose",
                  "version": 2
                },
                "combinator": "and",
                "conditions": [
                  {
                    "leftValue": "={{ $json.channel }}",
                    "rightValue": "linkedin",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ]
              },
              "outputKey": "linkedin"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": false,
                  "typeValidation": "loose",
                  "version": 2
                },
                "combinator": "and",
                "conditions": [
                  {
                    "leftValue": "={{ $json.channel }}",
                    "rightValue": "discord",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ]
              },
              "outputKey": "discord"
            }
          ]
        },
        "options": {}
      },
      "id": "rss-7-route",
      "name": "Route by Channel",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        1840,
        60
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.x.com/2/tweets",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "twitterOAuth1Api",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ text: $json.xText }) }}",
        "options": {}
      },
      "id": "rss-8a-x",
      "name": "X Post",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2040,
        -100
      ],
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.linkedin.com/v2/ugcPosts",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "linkedInOAuth2Api",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "X-Restli-Protocol-Version",
              "value": "2.0.0"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ author: 'urn:li:person:' + ($env.LINKEDIN_AUTHOR_URN || 'YOUR_PERSON_URN'), lifecycleState: 'PUBLISHED', specificContent: { 'com.linkedin.ugc.ShareContent': { shareCommentary: { text: $json.linkedinText }, shareMediaCategory: 'NONE' } }, visibility: { 'com.linkedin.ugc.MemberNetworkVisibility': 'PUBLIC' } }) }}",
        "options": {}
      },
      "id": "rss-8b-linkedin",
      "name": "LinkedIn Post",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2040,
        60
      ],
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $env.DISCORD_WEBHOOK_URL }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ content: $json.discordText, embeds: [{ title: $json.title, url: $json.link, description: ($json.summary || '').slice(0, 400), color: 1591619 }] }) }}",
        "options": {}
      },
      "id": "rss-8c-discord",
      "name": "Discord Webhook",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2040,
        220
      ],
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "jsCode": "// Aggregate channel results into a single per-cycle summary.\n// Reads the items emitted by the three social-channel nodes and tallies success / failure.\n\nconst items = $input.all();\nconst totals = { x: 0, linkedin: 0, discord: 0, total: items.length, errors: 0 };\nfor (const it of items) {\n  const ch = (it.json && it.json.channel) || 'unknown';\n  if (totals[ch] != null) totals[ch] += 1;\n}\nreturn [{ json: { ...totals, summarizedAt: new Date().toISOString() } }];"
      },
      "id": "rss-9-aggregate",
      "name": "Aggregate Results",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2240,
        60
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $env.SLACK_OPS_WEBHOOK }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ text: ':newspaper: RSS cycle posted ' + ($json.total || 0) + ' items (X=' + ($json.x || 0) + ', LinkedIn=' + ($json.linkedin || 0) + ', Discord=' + ($json.discord || 0) + ')' }) }}",
        "options": {}
      },
      "id": "rss-10-summary",
      "name": "Slack Summary",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2440,
        60
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "// Fallback for any failed channel post (X / LinkedIn / Discord).\n\nconst input = $input.first();\nconst err = (input.json && input.json.error) || input.error || {};\nconst orig = input.json || {};\n\nreturn [{\n  json: {\n    ok: false,\n    fallback: true,\n    channel: orig.channel || 'unknown',\n    title: orig.title || 'unknown',\n    link: orig.link || null,\n    error: {\n      message: err.message || 'unknown error',\n      name: err.name || 'SocialPostError',\n    },\n    receivedAt: new Date().toISOString(),\n  },\n}];"
      },
      "id": "rss-err-fallback",
      "name": "Error Fallback",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2240,
        380
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $env.SLACK_OPS_WEBHOOK }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ text: ':warning: RSS-Social channel error on ' + ($json.channel || 'unknown') + ': ' + ($json.error.message || '') + ' (item: ' + ($json.title || 'unknown') + ')' }) }}",
        "options": {}
      },
      "id": "rss-err-slack-alert",
      "name": "Error Slack Alert",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2440,
        380
      ],
      "onError": "continueRegularOutput"
    }
  ],
  "connections": {
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "List Feeds",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "List Feeds": {
      "main": [
        [
          {
            "node": "Rate Limit (opt-in)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Rate Limit (opt-in)": {
      "main": [
        [
          {
            "node": "Fetch RSS Feed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch RSS Feed": {
      "main": [
        [
          {
            "node": "Parse RSS Items",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Error Fallback",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse RSS Items": {
      "main": [
        [
          {
            "node": "Idempotency Filter (opt-in)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Idempotency Filter (opt-in)": {
      "main": [
        [
          {
            "node": "Normalize Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Items": {
      "main": [
        [
          {
            "node": "Set Channel Targets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Channel Targets": {
      "main": [
        [
          {
            "node": "Route by Channel",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by Channel": {
      "main": [
        [
          {
            "node": "X Post",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "LinkedIn Post",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Discord Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "X Post": {
      "main": [
        [
          {
            "node": "Aggregate Results",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Error Fallback",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LinkedIn Post": {
      "main": [
        [
          {
            "node": "Aggregate Results",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Error Fallback",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Discord Webhook": {
      "main": [
        [
          {
            "node": "Aggregate Results",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Error Fallback",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate Results": {
      "main": [
        [
          {
            "node": "Slack Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Error Fallback": {
      "main": [
        [
          {
            "node": "Error Slack Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

How this works

Stay effortlessly connected with your audience across multiple platforms by automating the distribution of fresh content from your favourite RSS feeds to X, LinkedIn, and Discord. This workflow fetches updates from specified sources, formats them into engaging posts, and shares them directly to each channel, saving hours of manual sharing and ensuring consistent visibility. Ideal for content creators, marketers, or community managers who juggle multiple social presences, the key step involves parsing RSS items via HTTP requests before routing tailored messages to each integration.

Use this workflow when you need reliable, scheduled posting from RSS sources to maintain an active online presence without daily effort, such as for newsletters or industry blogs. Avoid it for real-time news requiring instant alerts, as the cron trigger operates on fixed intervals like hourly or daily. Common variations include adding filters in the code nodes to select specific feed items or integrating email notifications for post confirmations.

About this workflow

RSS to Multi-Channel Social (X / LinkedIn / Discord). Uses stickyNote, scheduleTrigger, httpRequest. Scheduled trigger; 19 nodes.

Source: https://github.com/studiomeyer-io/n8n-workflows/blob/main/templates/08-rss-to-multi-channel-social/workflow.json — original creator credit. Request a take-down →

More General workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

General

WF-Main - XHS 主控制器. Uses scheduleTrigger, httpRequest, executeWorkflow, noOp. Scheduled trigger; 21 nodes.

HTTP Request
General

Dm-Profile-Visitors. Uses httpRequest, googleSheets. Scheduled trigger; 21 nodes.

HTTP Request, Google Sheets
General

YouTube Channel to Notion. Uses stickyNote, scheduleTrigger, httpRequest, noOp. Scheduled trigger; 18 nodes.

HTTP Request
General

Automate Droplet Snapshots On Digitalocean. Uses httpRequest, stickyNote. Scheduled trigger; 17 nodes.

HTTP Request
General

Calendar Conflict Detector (Google / Outlook). Uses stickyNote, scheduleTrigger, httpRequest. Scheduled trigger; 16 nodes.

HTTP Request