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 →
{
"id": "6c34e8d4-725c-4a74-909f-e2a1c3fe3421",
"name": "Send a coffee newsletter at 09:00 local time in every region",
"nodes": [
{
"parameters": {
"name": "Morning coffee newsletter",
"input": "Send the morning coffee newsletter every day at 9am local time in Paris, New York, Tokyo, and Perth",
"timezone": "UTC",
"onDeactivate": "pause"
},
"id": "2abf1afd-e8b8-4b84-93cd-68c4d5ea61e6",
"name": "At 09:00 in each target timezone",
"type": "@rrulenet/n8n-nodes-rrulenet.rruleNetScheduleTrigger",
"typeVersion": 1,
"position": [
-920,
-80
]
},
{
"parameters": {},
"id": "d56ca913-fa14-4d76-a7ba-fbe797861d89",
"name": "When clicking 'Execute workflow'",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
-920,
240
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "sample-schedule-id",
"name": "schedule_id",
"value": "manual-preview",
"type": "string"
},
{
"id": "sample-schedule-name",
"name": "schedule_name",
"value": "Morning coffee newsletter preview",
"type": "string"
},
{
"id": "sample-execution-id",
"name": "execution_id",
"value": "manual-preview-paris",
"type": "string"
},
{
"id": "sample-scheduled-for",
"name": "scheduled_for",
"value": "={{ $now.setZone('Europe/Paris').toISO() }}",
"type": "string"
},
{
"id": "sample-timezone",
"name": "timezone",
"value": "Europe/Paris",
"type": "string"
},
{
"id": "sample-target-id",
"name": "target_id",
"value": "paris",
"type": "string"
},
{
"id": "sample-target-label",
"name": "target_label",
"value": "Paris",
"type": "string"
},
{
"id": "sample-target-timezone",
"name": "target_timezone",
"value": "Europe/Paris",
"type": "string"
},
{
"id": "sample-target-metadata",
"name": "target_metadata",
"value": "={{ { city: 'Paris', timezone: 'Europe/Paris', preview: true } }}",
"type": "object"
},
{
"id": "sample-input-type",
"name": "input_type",
"value": "manual-preview",
"type": "string"
}
]
},
"options": {}
},
"id": "d4d03c2b-f38d-4c8d-8f35-d8ef05a0f4da",
"name": "Use a sample Paris execution",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
-680,
240
]
},
{
"parameters": {
"jsCode": "// Replace every example.com address before publishing the workflow.\nconst shared = {\n sender: 'Coffee Newsletter <newsletter@example.com>',\n feed_url: 'https://news.ycombinator.com/rss',\n max_articles: 5,\n};\n\nconst audiences = {\n paris: {\n timezone: 'Europe/Paris',\n locale: 'fr-FR',\n recipient: 'paris-team@example.com',\n greeting: 'Bonjour Paris',\n subject_prefix: 'Votre pause caf\u00e9',\n intro: 'Voici les articles \u00e0 d\u00e9couvrir avec votre caf\u00e9 ce matin.',\n footer: 'Envoy\u00e9 \u00e0 09:00 heure locale gr\u00e2ce \u00e0 rrule.net et n8n.',\n },\n new_york: {\n timezone: 'America/New_York',\n locale: 'en-US',\n recipient: 'new-york-team@example.com',\n greeting: 'Good morning New York',\n subject_prefix: 'Your morning coffee',\n intro: 'Here are a few stories to explore with your coffee this morning.',\n footer: 'Delivered at 09:00 local time with rrule.net and n8n.',\n },\n tokyo: {\n timezone: 'Asia/Tokyo',\n locale: 'ja-JP',\n recipient: 'tokyo-team@example.com',\n greeting: '\u304a\u306f\u3088\u3046\u3054\u3056\u3044\u307e\u3059\u3001\u6771\u4eac',\n subject_prefix: '\u671d\u306e\u30b3\u30fc\u30d2\u30fc\u30cb\u30e5\u30fc\u30b9',\n intro: '\u4eca\u671d\u306e\u30b3\u30fc\u30d2\u30fc\u3068\u4e00\u7dd2\u306b\u8aad\u307f\u305f\u3044\u8a18\u4e8b\u3092\u304a\u5c4a\u3051\u3057\u307e\u3059\u3002',\n footer: 'rrule.net \u3068 n8n \u306b\u3088\u308a\u73fe\u5730\u6642\u959309:00\u306b\u914d\u4fe1\u3055\u308c\u307e\u3057\u305f\u3002',\n },\n perth: {\n timezone: 'Australia/Perth',\n locale: 'en-AU',\n recipient: 'perth-team@example.com',\n greeting: 'Good morning Perth',\n subject_prefix: 'Your morning coffee',\n intro: 'Here are a few stories to explore with your coffee this morning.',\n footer: 'Delivered at 09:00 local time with rrule.net and n8n.',\n },\n};\n\nreturn $input.all().map((item, itemIndex) => {\n const targetId = String(item.json.target_id ?? '');\n const audience = audiences[targetId];\n\n if (!audience) {\n throw new Error(`No audience configuration exists for target_id ${targetId}. Update the audiences map before publishing.`);\n }\n\n if (item.json.target_timezone && item.json.target_timezone !== audience.timezone) {\n throw new Error(`Unexpected timezone ${item.json.target_timezone} for target_id ${targetId}. Expected ${audience.timezone}.`);\n }\n\n return {\n json: {\n ...item.json,\n ...shared,\n ...audience,\n },\n pairedItem: { item: itemIndex },\n };\n});"
},
"id": "e9b540fb-e2aa-47a8-b87e-0cde5259dbb8",
"name": "Configure regional audiences",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-440,
-80
]
},
{
"parameters": {
"url": "={{ $json.feed_url }}",
"options": {}
},
"id": "18d2e9cc-ca89-4ecf-990a-2a1b08b093b8",
"name": "Read the morning RSS feed",
"type": "n8n-nodes-base.rssFeedRead",
"typeVersion": 1.2,
"retryOnFail": true,
"maxTries": 3,
"waitBetweenTries": 5000,
"position": [
-180,
-80
]
},
{
"parameters": {
"jsCode": "const config = $('Configure regional audiences').first().json;\n\nconst escapeHtml = (value) => String(value ?? '')\n .replaceAll('&', '&')\n .replaceAll('<', '<')\n .replaceAll('>', '>')\n .replaceAll('\\\"', '"')\n .replaceAll(\"'\", ''');\n\nconst safeUrl = (value) => {\n const url = String(value ?? '').trim();\n return /^https?:\\/\\/[^\\s<>\"']+$/i.test(url) ? url : '#';\n};\n\nconst articleLimit = Math.max(1, Math.min(10, Number(config.max_articles) || 5));\nconst articles = $input.all()\n .map((item) => item.json)\n .filter((article) => typeof article.title === 'string' && typeof article.link === 'string')\n .slice(0, articleLimit);\n\nif (articles.length === 0) {\n throw new Error('The RSS feed returned no usable articles. Check feed_url before publishing.');\n}\n\nconst scheduledDate = new Date(String(config.scheduled_for || new Date().toISOString()));\nconst displayDate = new Intl.DateTimeFormat(String(config.locale), {\n dateStyle: 'full',\n timeZone: String(config.timezone),\n}).format(scheduledDate);\n\nconst articleHtml = articles.map((article) => {\n const title = escapeHtml(article.title);\n const link = escapeHtml(safeUrl(article.link));\n const rawSnippet = String(article.contentSnippet ?? '').trim();\n const snippet = /^Article URL:/i.test(rawSnippet) ? '' : escapeHtml(rawSnippet.slice(0, 240));\n const description = snippet ? `<p style=\\\"margin:8px 0 0;color:#5f5149;font-size:14px;font-weight:400;line-height:1.5;\\\">${snippet}</p>` : '';\n return `<li style=\\\"margin:0 0 22px;padding-left:4px;color:#6f3f21;font-size:17px;font-weight:700;line-height:1.4;\\\"><a href=\\\"${link}\\\" style=\\\"color:#6f3f21;font-size:17px;font-weight:700;line-height:1.4;text-decoration:none;\\\">${title}</a>${description}</li>`;\n}).join('');\n\nconst html = `<!doctype html>\n<html lang=\\\"${escapeHtml(String(config.locale).split('-')[0] || 'en')}\\\">\n <head>\n <meta charset=\\\"utf-8\\\">\n <meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1\\\">\n </head>\n <body style=\\\"margin:0;background:#f7f1eb;font-family:Arial,sans-serif;color:#2d241f;\\\">\n <div style=\\\"display:none;max-height:0;overflow:hidden;opacity:0;color:transparent;\\\">${escapeHtml(config.intro)}</div>\n <table role=\\\"presentation\\\" width=\\\"100%\\\" cellspacing=\\\"0\\\" cellpadding=\\\"0\\\" style=\\\"background:#f7f1eb;padding:24px 12px;\\\">\n <tr><td align=\\\"center\\\">\n <table role=\\\"presentation\\\" width=\\\"100%\\\" cellspacing=\\\"0\\\" cellpadding=\\\"0\\\" style=\\\"max-width:640px;background:#ffffff;border-radius:14px;overflow:hidden;\\\">\n <tr><td style=\\\"background:#5b3825;padding:28px 32px;color:#ffffff;\\\">\n <div style=\\\"font-size:13px;letter-spacing:.08em;text-transform:uppercase;opacity:.8;\\\">${escapeHtml(displayDate)}</div>\n <h1 style=\\\"margin:10px 0 0;font-size:28px;\\\">\u2615 ${escapeHtml(config.greeting)}</h1>\n </td></tr>\n <tr><td style=\\\"padding:30px 32px;\\\">\n <p style=\\\"margin:0 0 24px;font-size:16px;line-height:1.6;\\\">${escapeHtml(config.intro)}</p>\n <ol style=\\\"margin:0;padding-left:24px;\\\">${articleHtml}</ol>\n </td></tr>\n <tr><td style=\\\"padding:20px 32px;background:#efe3d7;color:#6b5a50;font-size:12px;line-height:1.5;\\\">\n ${escapeHtml(config.footer)}<br>Target: ${escapeHtml(config.target_id)} \u00b7 ${escapeHtml(config.target_timezone)}\n </td></tr>\n </table>\n </td></tr>\n </table>\n </body>\n</html>`;\n\nreturn [{\n json: {\n execution_id: config.execution_id,\n scheduled_for: config.scheduled_for,\n target_id: config.target_id,\n target_timezone: config.target_timezone,\n recipient: config.recipient,\n sender: config.sender,\n subject: `${config.subject_prefix} \u2014 ${displayDate}`,\n html,\n article_count: articles.length,\n },\n}];"
},
"id": "636484f7-bd90-4615-b6b3-f54e54d64227",
"name": "Build localized newsletter",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
80,
-80
]
},
{
"parameters": {
"resource": "email",
"operation": "send",
"sendHTML": true,
"subject": "={{ $json.subject }}",
"htmlContent": "={{ $json.html }}",
"sender": "={{ $json.sender }}",
"receipients": "={{ $json.recipient }}",
"additionalFields": {
"emailTags": {
"tags": {
"tag": "rrulenet,coffee-newsletter"
}
}
}
},
"id": "33bf7781-5b92-4105-aaed-78de5b3863e0",
"name": "Send with Brevo",
"type": "n8n-nodes-base.sendInBlue",
"typeVersion": 1,
"position": [
340,
-80
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "result-message-id",
"name": "message_id",
"value": "={{ $json.messageId }}",
"type": "string"
},
{
"id": "result-execution-id",
"name": "execution_id",
"value": "={{ $('Build localized newsletter').first().json.execution_id }}",
"type": "string"
},
{
"id": "result-scheduled-for",
"name": "scheduled_for",
"value": "={{ $('Build localized newsletter').first().json.scheduled_for }}",
"type": "string"
},
{
"id": "result-target-id",
"name": "target_id",
"value": "={{ $('Build localized newsletter').first().json.target_id }}",
"type": "string"
},
{
"id": "result-target-timezone",
"name": "target_timezone",
"value": "={{ $('Build localized newsletter').first().json.target_timezone }}",
"type": "string"
},
{
"id": "result-recipient",
"name": "recipient",
"value": "={{ $('Build localized newsletter').first().json.recipient }}",
"type": "string"
},
{
"id": "result-article-count",
"name": "article_count",
"value": "={{ $('Build localized newsletter').first().json.article_count }}",
"type": "number"
}
]
},
"options": {}
},
"id": "19ad56f4-4a2f-411e-90e5-96703e00234c",
"name": "Return delivery context",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
600,
-80
]
},
{
"parameters": {
"content": "## Setup before publishing\n\n1. Install `@rrulenet/n8n-nodes-rrulenet`.\n2. Add rrule.net and Brevo credentials.\n3. Edit the sender and four recipients in **Configure regional audiences**.\n4. Run the manual Paris preview.\n5. Publish the workflow only after the preview succeeds.",
"height": 220,
"width": 420
},
"id": "df9679c7-a3a3-49b7-a95f-7fc29dada85e",
"name": "Setup",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-940,
-420
]
},
{
"parameters": {
"content": "## Why rrule.net?\n\nThis is one logical schedule with four targets. Paris and New York keep 09:00 wall-clock time through DST changes; Tokyo and Perth remain on their non-DST offsets. Route each execution with `target_id` and verify `target_timezone`.",
"height": 180,
"width": 420
},
"id": "22724e22-31dd-4ea6-9898-34a094b02574",
"name": "Local-time scheduling",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-480,
-420
]
},
{
"parameters": {
"content": "## Replaceable content\n\nThe RSS feed and five-article limit are configured in **Configure regional audiences**. The HTML builder escapes feed text and only links to HTTP(S) URLs. Brevo sends one transactional digest per target; adapt consent and unsubscribe handling before using it as a public marketing newsletter.",
"height": 200,
"width": 500
},
"id": "8c359270-7f49-4ff9-9c35-352fa89b952e",
"name": "Content and email",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
0,
-420
]
}
],
"connections": {
"At 09:00 in each target timezone": {
"main": [
[
{
"node": "Configure regional audiences",
"type": "main",
"index": 0
}
]
]
},
"When clicking 'Execute workflow'": {
"main": [
[
{
"node": "Use a sample Paris execution",
"type": "main",
"index": 0
}
]
]
},
"Use a sample Paris execution": {
"main": [
[
{
"node": "Configure regional audiences",
"type": "main",
"index": 0
}
]
]
},
"Configure regional audiences": {
"main": [
[
{
"node": "Read the morning RSS feed",
"type": "main",
"index": 0
}
]
]
},
"Read the morning RSS feed": {
"main": [
[
{
"node": "Build localized newsletter",
"type": "main",
"index": 0
}
]
]
},
"Build localized newsletter": {
"main": [
[
{
"node": "Send with Brevo",
"type": "main",
"index": 0
}
]
]
},
"Send with Brevo": {
"main": [
[
{
"node": "Return delivery context",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "a9d67525-22b0-46e6-bd3f-41e031ebeb14",
"meta": {
"templateCredsSetupCompleted": false
},
"tags": []
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Send a coffee newsletter at 09:00 local time in every region. Uses @rrulenet/n8n-nodes-rrulenet, rssFeedRead, sendInBlue. Scheduled trigger; 11 nodes.
Source: https://github.com/rrulenet/n8n-nodes-rrulenet/blob/main/templates/coffee-newsletter/coffee-newsletter.workflow.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Fetch an RSS feed, build a localized morning digest, and send it with Brevo at 09:00 local time in Paris, New York, Tokyo, and Perth from one target-aware rrule.net schedule. One rrule.net Schedule Tr
This workflow runs hourly to read multiple RSS/Atom feeds, applies rule-based topic matching, relevance scoring, sentiment and entity tagging with deduplication, and then sends scored HTML email diges
World News Collector (10m). Uses rssFeedRead, dataTable. Scheduled trigger; 11 nodes.
Job Seeking. Uses scheduleTrigger, rssFeedRead, googleSheets, httpRequest. Scheduled trigger; 12 nodes.
Finance Alert. Uses rssFeedRead, openAi, httpRequest. Scheduled trigger; 11 nodes.