This workflow corresponds to n8n.io template #16507 — we link there as the canonical source.
This workflow follows the Emailsend → HTTP Request recipe pattern — see all workflows that pair these two integrations.
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 →
{
"name": "Aumadi \u2014 Daily Product Pulse Email",
"tags": [],
"nodes": [
{
"id": "8b820e94-5fce-47fb-8a3e-ad85d5ba73f4",
"name": "Section \u2014 Understand the DB",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2540,
300
],
"parameters": {
"color": 7,
"width": 800,
"height": 440,
"content": "## \u2460 Read your requirement + live schema\n\nThe schedule fires. **Discover Schema** reads your Supabase OpenAPI (every table + column). **Read Schema** condenses it so Claude can only reference what truly exists."
},
"typeVersion": 1
},
{
"id": "1b77df16-2b61-447d-8f6f-5405a0c8b81b",
"name": "Section \u2014 Plan + guard",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1680,
300
],
"parameters": {
"color": 7,
"width": 440,
"height": 440,
"content": "## \u2461 Plan the queries (then guard them)\n\n**Plan Queries**: Claude builds PostgREST queries using only your real tables/columns + placeholder dates. **Validate & Resolve** drops anything whose table is not in your DB and fills in real timestamps \u2014 so a wrong query never runs."
},
"typeVersion": 1
},
{
"id": "d7d68d9e-036b-479a-a564-f10237f1f169",
"name": "Section \u2014 Run + report",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1180,
300
],
"parameters": {
"color": 7,
"width": 1620,
"height": 560,
"content": "## \u2462 Run \u2192 count \u2192 guard \u2192 report\n\n**Run Query** executes each query; **Collect Results** counts/sums/groups. **Found requirement data?** routes: matched \u2192 **Write Report** \u2192 email; matched nothing \u2192 a clear \"not found\" email instead of a blank report."
},
"typeVersion": 1
},
{
"id": "3c74caca-26e0-4522-8c35-975e1b76b0e6",
"name": "\ud83d\udcd8 Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3120,
300
],
"parameters": {
"width": 540,
"height": 480,
"content": "## \ud83e\udde0 Adaptive Daily Pulse (you write the requirement)\n\n### Who's it for\nAnyone on Supabase who wants a morning report \u2014 but defined in **plain English**, not hardcoded. Point it at any Supabase project and it adapts.\n\n### How it works\n1. You write what you want in **\u2699\ufe0f Config \u2192 requirements** (free text).\n2. The workflow **reads your live Supabase schema** (which tables/columns exist).\n3. **Claude** plans the exact queries \u2014 **using only tables/columns that exist**, with safe PostgREST syntax + placeholder dates.\n4. A **validator** drops any query whose table isn't in your DB (never a wrong query).\n5. Queries run over the **Supabase API**, results are computed in n8n, **Claude writes the report**, and it's emailed.\n\n### Zero setup, simple credential\nNo functions, no Postgres connection \u2014 just your normal **Supabase API** credential."
},
"typeVersion": 1
},
{
"id": "db8395ed-43ea-46d3-9dd5-46f688c23161",
"name": "\ud83d\udd27 Setup",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3120,
820
],
"parameters": {
"width": 540,
"height": 320,
"content": "## \ud83d\udd27 Setup\n1. Credentials: **Supabase API** (host + service key), **Anthropic** (Header Auth `x-api-key`), **SMTP**.\n2. In **\u2699\ufe0f Config**: set `supabaseUrl`, `reportEmail`, `fromEmail`, `brandName`, `windowHours`, and your **requirements** (plain English \u2014 e.g. *how many orders in the last 24h and revenue, new signups, top restaurants, and how many users are now inactive 30+ days*).\n3. Set the **schedule** to your hour/timezone, then Activate (or 'Test workflow').\n\nChange the **requirements** text anytime \u2192 the report changes. Things your DB doesn't have are listed as 'skipped', never guessed."
},
"typeVersion": 1
},
{
"id": "f710cb5f-cb1a-4285-98aa-894bfedd4fb4",
"name": "Every Morning 8am",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-2480,
590
],
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 8
}
]
}
},
"typeVersion": 1.2
},
{
"id": "4f89cf38-74f4-4c2d-91c8-60f6e153ca73",
"name": "\u2699\ufe0f Config",
"type": "n8n-nodes-base.set",
"position": [
-2280,
590
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "c_url",
"name": "supabaseUrl",
"type": "string",
"value": "https://YOUR_PROJECT.supabase.co"
},
{
"id": "c_to",
"name": "reportEmail",
"type": "string",
"value": "user@example.com"
},
{
"id": "c_from",
"name": "fromEmail",
"type": "string",
"value": "user@example.com"
},
{
"id": "c_brand",
"name": "brandName",
"type": "string",
"value": "Your Company"
},
{
"id": "c_win",
"name": "windowHours",
"type": "number",
"value": 24
},
{
"id": "c_model",
"name": "textModel",
"type": "string",
"value": "claude-haiku-4-5-20251001"
},
{
"id": "c_req",
"name": "requirements",
"type": "string",
"value": "For the last 1 week, using the demo_ SaaS tables: how many new signups (demo_users), total revenue from successful payments and how many payments failed (demo_payments), how many users are active vs inactive with no activity for 30+ days (demo_users.last_active_at), signups broken down by country (demo_users.country), how many subscriptions are currently canceled (demo_subscriptions), and how many support tickets are open by priority (demo_tickets)."
},
{
"id": "da810d60-4f42-43a1-b197-4589e1c86c76",
"name": "planSystem",
"type": "string",
"value": "You are a careful analytics QUERY PLANNER. You convert a plain-English reporting REQUIREMENT into safe, correct Supabase PostgREST read queries, using ONLY the tables and columns in the SCHEMA you are given. RULES \u2014 follow exactly: 1. ONLY use table names and column names that appear verbatim in SCHEMA. NEVER invent a table or column. If a requested metric needs something not in SCHEMA, do NOT query it \u2014 list it under \"skipped\" with a short reason. 2. For any date/time, use ONLY these placeholder tokens (replaced later): NOW, SINCE_WINDOW, SINCE_24H, SINCE_48H, SINCE_72H, SINCE_7D, SINCE_30D, SINCE_31D, SINCE_90D. Never write a literal date. 3. Each \"query\" is a PostgREST query string (everything AFTER the table name): filters like column=eq.value | =gte.value | =lt.value | =is.null ; combine with & ; OR via or=(column.lt.SINCE_30D,column.is.null) ; select=col1,col2 ; embed: select=col1,related_table(col) ; a timestamp uses a placeholder e.g. created_at=gte.SINCE_WINDOW. 4. Pick an \"op\": \"count\" (count rows; select one small real column), \"sum\" (set value_column to a numeric column), or \"group\" (set group_column to a column or embedded like restaurants.branch_name; top 5). 5. Only build metrics the REQUIREMENT asks for. Use SINCE_WINDOW for the main window unless a specific window is named (e.g. 30 days -> SINCE_30D). 6. Output ONLY a raw JSON object \u2014 no markdown, no code fences. OUTPUT SHAPE (example \u2014 use the REAL schema): { \"metrics\": [ { \"id\": \"new_signups\", \"label\": \"New signups (24h)\", \"table\": \"demo_users\", \"query\": \"select=created_at&created_at=gte.SINCE_WINDOW\", \"op\": \"count\" }, { \"id\": \"revenue\", \"label\": \"Revenue (24h)\", \"table\": \"demo_payments\", \"query\": \"select=amount&status=eq.succeeded&created_at=gte.SINCE_WINDOW\", \"op\": \"sum\", \"value_column\": \"amount\" } ], \"skipped\": [ { \"requested\": \"refunds\", \"reason\": \"no refunds table\" } ] } When unsure, SKIP rather than guess."
},
{
"id": "8830437d-c09d-4ede-91b7-97977ce3bb55",
"name": "reportSystem",
"type": "string",
"value": "You write a SHORT daily pulse email from already-computed metrics. You are given the original requirement and a results array (each item has label, op, value). Reply with ONLY a raw JSON object (no markdown) with keys: headline (one upbeat line), summary (2-3 plain sentences citing the actual numbers), insights (array of 2-3 short flags). Warm, concrete, no jargon."
}
]
}
},
"typeVersion": 3.4
},
{
"id": "40d7ec84-837c-4da3-9382-5a8ecc8f7187",
"name": "\ud83d\uddc2\ufe0f Discover Schema",
"type": "n8n-nodes-base.httpRequest",
"position": [
-2080,
590
],
"parameters": {
"url": "={{ $('\u2699\ufe0f Config').first().json.supabaseUrl }}/rest/v1/",
"options": {
"timeout": 20000
},
"sendHeaders": true,
"authentication": "predefinedCredentialType",
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "application/openapi+json"
}
]
},
"nodeCredentialType": "supabaseApi"
},
"credentials": {
"supabaseApi": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "26ae3824-967b-4f8a-bd13-a6720e732f42",
"name": "\ud83e\udde9 Read Schema",
"type": "n8n-nodes-base.code",
"position": [
-1860,
590
],
"parameters": {
"jsCode": "let raw = $input.first().json;\nlet body = raw;\nif (raw && raw.body !== undefined) body = raw.body;\nelse if (raw && raw.data !== undefined) body = raw.data;\nif (typeof body === 'string') { try { body = JSON.parse(body); } catch (e) {} }\nconst defs = (body && (body.definitions || (body.components && body.components.schemas))) || {};\nconst tableMap = {}; const lines = [];\nfor (const t of Object.keys(defs)) {\n const props = (defs[t] && defs[t].properties) ? Object.keys(defs[t].properties) : [];\n tableMap[t] = props;\n lines.push(t + '(' + props.join(', ') + ')');\n}\nreturn [{ json: { schemaText: lines.join('\\n'), tableMap: tableMap, tableCount: lines.length } }];\n"
},
"typeVersion": 2
},
{
"id": "e2c12f85-4348-4b72-b26f-d7960410b900",
"name": "\ud83e\udde0 Plan Queries",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"position": [
-1620,
590
],
"parameters": {
"url": "https://api.anthropic.com/v1/messages",
"method": "POST",
"options": {
"timeout": 40000
},
"jsonBody": "={{ JSON.stringify({ model: $('\u2699\ufe0f Config').first().json.textModel, max_tokens: 1500, system: $('\u2699\ufe0f Config').first().json.planSystem, messages: [ { role: 'user', content: 'REQUIREMENT:\\n' + $('\u2699\ufe0f Config').first().json.requirements + '\\n\\nMain window: last ' + $('\u2699\ufe0f Config').first().json.windowHours + ' hours (use SINCE_WINDOW).\\n\\nSCHEMA (only these tables/columns exist):\\n' + $json.schemaText + '\\n\\nPlan the metrics as JSON now.' } ] }) }}\n",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"headerParameters": {
"parameters": [
{
"name": "content-type",
"value": "application/json"
},
{
"name": "anthropic-version",
"value": "2023-06-01"
}
]
}
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "4a0faed7-98ee-4813-aa04-7a61ad612311",
"name": "\u2705 Validate & Resolve",
"type": "n8n-nodes-base.code",
"position": [
-1400,
590
],
"parameters": {
"jsCode": "const c = $input.first().json;\nlet txt = '';\ntry { if (Array.isArray(c.content)) txt = c.content.map(x => x.text || '').join(''); } catch (e) {}\ntxt = String(txt).trim().replace(/^\\x60{3}(?:json)?/i, '').replace(/\\x60{3}$/, '').trim();\nlet plan = { metrics: [], skipped: [] };\ntry { plan = JSON.parse(txt); } catch (e) {}\n\nconst cfg = $('\u2699\ufe0f Config').first().json;\nconst tableMap = $('\ud83e\udde9 Read Schema').first().json.tableMap || {};\nconst base = String(cfg.supabaseUrl).replace(/\\/$/, '') + '/rest/v1/';\nconst now = Date.now(); const H = 3600 * 1000; const D = 24 * H;\nconst iso = ms => new Date(ms).toISOString();\nconst ph = {\n SINCE_WINDOW: iso(now - (Number(cfg.windowHours || 24)) * H),\n SINCE_24H: iso(now-24*H), SINCE_48H: iso(now-48*H), SINCE_72H: iso(now-72*H),\n SINCE_7D: iso(now-7*D), SINCE_30D: iso(now-30*D), SINCE_31D: iso(now-31*D), SINCE_90D: iso(now-90*D), NOW: iso(now)\n};\nconst resolve = s => { let out = String(s || ''); for (const k of Object.keys(ph)) { out = out.split(k).join(ph[k]); } return out; };\n\nconst skipped = Array.isArray(plan.skipped) ? plan.skipped.slice() : [];\nconst out = [];\nfor (const m of (plan.metrics || [])) {\n if (!m || !m.table || !tableMap[m.table]) { skipped.push({ requested: (m && m.label) || '(unknown)', reason: 'table not in your database' }); continue; }\n let q = resolve(m.query || '');\n if (q && q[0] === '?') q = q.slice(1);\n const url = base + m.table + '?' + (q ? q + '&' : '') + 'limit=10000';\n out.push({ json: { id: m.id || m.label, label: m.label || m.id || m.table, op: m.op || 'count', value_column: m.value_column || null, group_column: m.group_column || null, url: url, skipped: skipped } });\n}\nif (out.length === 0) { out.push({ json: { dummy: true, url: base, op: 'count', label: 'none', skipped: skipped } }); }\nreturn out;\n"
},
"typeVersion": 2
},
{
"id": "6645ef3b-301e-4f06-b6dd-5d89aa935ea8",
"name": "\ud83d\udd0e Run Query",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"position": [
-1120,
590
],
"parameters": {
"url": "={{ $json.url }}",
"options": {
"timeout": 20000,
"response": {
"response": {
"fullResponse": true
}
}
},
"sendHeaders": true,
"authentication": "predefinedCredentialType",
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "application/json"
}
]
},
"nodeCredentialType": "supabaseApi"
},
"credentials": {
"supabaseApi": {
"name": "<your credential>"
}
},
"typeVersion": 4.2,
"alwaysOutputData": true
},
{
"id": "497287b6-f93c-4b3d-ab6c-c768a58a85e9",
"name": "\ud83e\uddee Collect Results",
"type": "n8n-nodes-base.code",
"position": [
-900,
590
],
"parameters": {
"jsCode": "const metrics = $('\u2705 Validate & Resolve').all().map(i => i.json);\nconst resp = $('\ud83d\udd0e Run Query').all().map(i => i.json);\nconst results = [];\nfor (let i = 0; i < metrics.length; i++) {\n const m = metrics[i];\n if (m.dummy) continue;\n const r = resp[i] || {};\n const rows = (r && Array.isArray(r.body)) ? r.body : (Array.isArray(r) ? r : []);\n let value;\n if (m.op === 'sum') {\n value = Math.round(rows.reduce(function (s, x) { return s + Number((x && x[m.value_column]) || 0); }, 0));\n } else if (m.op === 'group') {\n const pm = {}; const gc = String(m.group_column || '');\n for (const x of rows) { let v = x; for (const part of gc.split('.')) { v = (v && typeof v === 'object') ? v[part] : undefined; } const key = (v == null ? 'Unknown' : String(v)); pm[key] = (pm[key] || 0) + 1; }\n value = Object.keys(pm).map(function (k) { return { key: k, count: pm[k] }; }).sort(function (a, b) { return b.count - a.count; }).slice(0, 5);\n } else { value = rows.length; }\n results.push({ id: m.id, label: m.label, op: m.op, value: value });\n}\nconst skipped = (metrics[0] && metrics[0].skipped) || [];\nreturn [{ json: { requirements: $('\u2699\ufe0f Config').first().json.requirements, results: results, skipped: skipped, resultCount: results.length } }];\n"
},
"typeVersion": 2
},
{
"id": "885a30fd-0b17-41ce-b82f-71815098d875",
"name": "\ud83e\udde0 Write Report",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"position": [
-440,
470
],
"parameters": {
"url": "https://api.anthropic.com/v1/messages",
"method": "POST",
"options": {
"timeout": 30000
},
"jsonBody": "={{ JSON.stringify({ model: $('\u2699\ufe0f Config').first().json.textModel, max_tokens: 500, system: $('\u2699\ufe0f Config').first().json.reportSystem, messages: [ { role: 'user', content: 'Brand: ' + $('\u2699\ufe0f Config').first().json.brandName + '\\nRequirement: ' + $json.requirements + '\\nResults JSON: ' + JSON.stringify($json.results) + '\\nSkipped: ' + JSON.stringify($json.skipped) + '\\n\\nWrite the report JSON.' } ] }) }}\n",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"headerParameters": {
"parameters": [
{
"name": "content-type",
"value": "application/json"
},
{
"name": "anthropic-version",
"value": "2023-06-01"
}
]
}
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "5471ff0f-6c2c-4fd2-93f0-714c6a759e05",
"name": "Extract Report",
"type": "n8n-nodes-base.code",
"position": [
-200,
470
],
"parameters": {
"jsCode": "const r = $input.first().json;\nlet t = '';\ntry { if (Array.isArray(r.content)) t = r.content.map(c => c.text || '').join(''); } catch (e) {}\nt = String(t).trim().replace(/^\\x60{3}(?:json)?/i, '').replace(/\\x60{3}$/, '').trim();\nlet o = { headline: 'Your daily pulse', summary: '', insights: [] };\ntry { const j = JSON.parse(t); o.headline = j.headline || o.headline; o.summary = j.summary || ''; o.insights = Array.isArray(j.insights) ? j.insights : []; } catch (e) {}\nreturn [{ json: o }];\n"
},
"typeVersion": 2
},
{
"id": "230aa2b0-1598-47ca-bc63-144e782386b1",
"name": "Build Email",
"type": "n8n-nodes-base.code",
"position": [
40,
470
],
"parameters": {
"jsCode": "const data = $('\ud83e\uddee Collect Results').first().json;\nconst rep = $input.first().json;\nconst cfg = $('\u2699\ufe0f Config').first().json;\nconst fmt = function (v) { return (v == null ? '0' : String(v)); };\nlet cards = '';\nfor (const m of (data.results || [])) {\n let val = (m.op === 'group')\n ? (Array.isArray(m.value) ? m.value.map(function (g) { return g.key + ': <b>' + g.count + '</b>'; }).join('<br>') : '')\n : '<b>' + fmt(m.value) + '</b>';\n cards += '<tr><td style=\"padding:10px 0;border-bottom:1px solid #eee;color:#374151;font-size:14px;vertical-align:top\">' + m.label + '</td><td style=\"padding:10px 0;border-bottom:1px solid #eee;text-align:right;color:#111827;font-size:14px\">' + val + '</td></tr>';\n}\nconst skipped = (data.skipped || []).map(function (s) { return '<li>' + s.requested + ' \u2014 ' + s.reason + '</li>'; }).join('');\nconst insights = (rep.insights || []).map(function (s) { return '<li style=\"margin-bottom:4px\">' + s + '</li>'; }).join('');\nconst html = '<!DOCTYPE html><html><body style=\"margin:0;background:#f4f4f5;font-family:Arial,Helvetica,sans-serif\"><table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"padding:24px 0\"><tr><td align=\"center\"><table width=\"600\" cellpadding=\"0\" cellspacing=\"0\" style=\"max-width:600px;background:#fff;border-radius:12px;overflow:hidden\"><tr><td style=\"background:#111827;padding:20px 28px;color:#fff;font-size:18px;font-weight:bold\">' + cfg.brandName + ' \u2014 Daily Pulse</td></tr><tr><td style=\"padding:24px 28px\"><h2 style=\"margin:0 0 6px;color:#111827;font-size:18px\">' + (rep.headline || '') + '</h2><p style=\"margin:0 0 16px;color:#374151;line-height:1.6;font-size:14px\">' + (rep.summary || '') + '</p><table width=\"100%\" cellspacing=\"0\">' + cards + '</table>' + (insights ? ('<h3 style=\"margin:20px 0 6px;font-size:14px;color:#111827\">Things to note</h3><ul style=\"margin:0;padding-left:18px;color:#374151;font-size:14px\">' + insights + '</ul>') : '') + (skipped ? ('<p style=\"margin:16px 0 4px;font-size:12px;color:#9ca3af\">Asked for but not in your database (skipped):</p><ul style=\"margin:0;padding-left:18px;color:#9ca3af;font-size:12px\">' + skipped + '</ul>') : '') + '</td></tr><tr><td style=\"padding:16px 28px;background:#f9fafb;color:#9ca3af;font-size:12px\">Generated by ' + cfg.brandName + '</td></tr></table></td></tr></table></body></html>';\nlet lines = '';\nfor (const m of (data.results || [])) { lines += m.label + ': ' + (m.op === 'group' ? (Array.isArray(m.value) ? m.value.map(function (g) { return g.key + '=' + g.count; }).join(', ') : '') : fmt(m.value)) + '\\n'; }\nreturn [{ json: { to: cfg.reportEmail, subject: cfg.brandName + ' \u2014 Daily Pulse', html: html, text: (rep.headline || 'Daily Pulse') + '\\n\\n' + (rep.summary || '') + '\\n\\n' + lines } }];\n"
},
"typeVersion": 2
},
{
"id": "fb662269-71a1-4de7-9eb0-5a2c9a831834",
"name": "\u2709\ufe0f Send Report",
"type": "n8n-nodes-base.emailSend",
"onError": "continueRegularOutput",
"position": [
280,
590
],
"parameters": {
"html": "={{ $json.html }}",
"text": "={{ $json.text }}",
"options": {},
"subject": "={{ $json.subject }}",
"toEmail": "={{ $json.to }}",
"fromEmail": "={{ $('\u2699\ufe0f Config').first().json.fromEmail }}",
"emailFormat": "both"
},
"credentials": {
"smtp": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "48f3b2d6-0ff1-43ad-882c-ca63278ba36b",
"name": "Found requirement data?",
"type": "n8n-nodes-base.if",
"position": [
-680,
590
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "6f108f33-395b-4e21-aa1e-94d057a0a58c",
"operator": {
"type": "number",
"operation": "gt"
},
"leftValue": "={{ $json.resultCount }}",
"rightValue": 0
}
]
}
},
"typeVersion": 2.3
},
{
"id": "79986302-5df4-4a9a-8feb-a6fde794e25a",
"name": "\ud83d\udedf Build Not-Found Email",
"type": "n8n-nodes-base.code",
"position": [
-440,
730
],
"parameters": {
"jsCode": "const data = $('\ud83e\uddee Collect Results').first().json;\nconst cfg = $('\u2699\ufe0f Config').first().json;\nconst skipped = (data.skipped || []).map(function (s) { return '<li>' + s.requested + ' \u2014 ' + s.reason + '</li>'; }).join('') || '<li>(nothing matched your requirement)</li>';\nconst text = \"The Daily Pulse ran, but none of the tables your requirement asked for were found, so there's nothing to report.\\n\\nLikely causes:\\n1) Table names in your requirement don't match your schema.\\n2) Tables exist but the API can't see them - run NOTIFY pgrst, 'reload schema'; in Supabase, then retry.\\n3) Wrong Supabase API credential / project URL.\";\nconst html = \"<!DOCTYPE html><html><body style='font-family:Arial,Helvetica,sans-serif;background:#f4f4f5;margin:0;padding:24px'><table width='600' align='center' cellpadding='0' cellspacing='0' style='background:#fff;border-radius:12px;overflow:hidden'><tr><td style='background:#b45309;color:#fff;padding:20px 28px;font-size:18px;font-weight:bold'>\" + cfg.brandName + \" \u2014 no matching data for your requirement</td></tr><tr><td style='padding:24px 28px;color:#374151;font-size:14px;line-height:1.6'>The Pulse ran, but <b>none of the tables your requirement asked for were found</b>.<br><br><b>Asked for but not found:</b><ul>\" + skipped + \"</ul><b>How to fix:</b><ul><li>Match the table names in your requirement to your schema</li><li>If the tables exist, run <code>NOTIFY pgrst, 'reload schema';</code> then retry</li><li>Check the Supabase API credential + project URL</li></ul></td></tr></table></body></html>\";\nreturn [{ json: { to: cfg.reportEmail, subject: cfg.brandName + \" \u2014 no matching data for your requirement\", html: html, text: text } }];\n"
},
"typeVersion": 2
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"connections": {
"Build Email": {
"main": [
[
{
"node": "\u2709\ufe0f Send Report",
"type": "main",
"index": 0
}
]
]
},
"\u2699\ufe0f Config": {
"main": [
[
{
"node": "\ud83d\uddc2\ufe0f Discover Schema",
"type": "main",
"index": 0
}
]
]
},
"Extract Report": {
"main": [
[
{
"node": "Build Email",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udd0e Run Query": {
"main": [
[
{
"node": "\ud83e\uddee Collect Results",
"type": "main",
"index": 0
}
]
]
},
"\ud83e\udde9 Read Schema": {
"main": [
[
{
"node": "\ud83e\udde0 Plan Queries",
"type": "main",
"index": 0
}
]
]
},
"Every Morning 8am": {
"main": [
[
{
"node": "\u2699\ufe0f Config",
"type": "main",
"index": 0
}
]
]
},
"\ud83e\udde0 Plan Queries": {
"main": [
[
{
"node": "\u2705 Validate & Resolve",
"type": "main",
"index": 0
}
]
]
},
"\ud83e\udde0 Write Report": {
"main": [
[
{
"node": "Extract Report",
"type": "main",
"index": 0
}
]
]
},
"\ud83e\uddee Collect Results": {
"main": [
[
{
"node": "Found requirement data?",
"type": "main",
"index": 0
}
]
]
},
"\u2705 Validate & Resolve": {
"main": [
[
{
"node": "\ud83d\udd0e Run Query",
"type": "main",
"index": 0
}
]
]
},
"Found requirement data?": {
"main": [
[
{
"node": "\ud83e\udde0 Write Report",
"type": "main",
"index": 0
}
],
[
{
"node": "\ud83d\udedf Build Not-Found Email",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\uddc2\ufe0f Discover Schema": {
"main": [
[
{
"node": "\ud83e\udde9 Read Schema",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udedf Build Not-Found Email": {
"main": [
[
{
"node": "\u2709\ufe0f Send Report",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
httpHeaderAuthsmtpsupabaseApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This scheduled workflow reads your Supabase project’s live schema, uses Anthropic Claude to plan safe PostgREST queries from a plain-English requirement, computes the requested metrics, and sends a daily pulse email via SMTP with results or a clear “no matching data” notice.…
Source: https://n8n.io/workflows/16507/ — 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.
Disparador 1.8. Uses itemLists, postgres, emailSend, httpRequest. Scheduled trigger; 85 nodes.
This n8n workflow runs daily to analyze active customer behavior, engineers relevant features from usage and transaction data, applies a machine learning or AI-based model to predict churn probability
This n8n workflow automates continuous compliance monitoring across IT, OT, and cloud environments by aggregating security controls, validating policies (ISO 27001, NIST, GDPR, SOC2), detecting anomal
Automates real-time market monitoring, technical analysis, AI-powered signal generation for cryptocurrencies (and stocks), filters high-confidence trades, and delivers actionable alerts via multiple c
HIPAA-Compliant Healthcare Data Synchronization. Uses httpRequest, postgres, mongoDb, elasticsearch. Scheduled trigger; 19 nodes.