This workflow follows the Gmail → 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": "bridge_marketing",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "bridge-marketing-trigger",
"responseMode": "responseNode",
"options": {}
},
"id": "node_trigger",
"name": "Trigger",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
240,
400
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "WITH picked AS (\n SELECT l.lead_id, l.business_name, l.normalized_business_name, l.category,\n l.city, l.region, l.postcode, l.country,\n l.phone_raw, l.email, l.email_status,\n l.website_url_found, l.reviews_rating, l.reviews_count,\n wp.slug AS demo_slug,\n wp.preview_url AS demo_preview_url,\n wp.copy_json AS demo_copy\n FROM bridge.leads l\n LEFT JOIN bridge.website_projects wp ON wp.lead_id = l.lead_id\n AND wp.deployment_status = 'live'\n WHERE l.website_status = 'Ready for Marketing'\n AND l.marketing_status = 'Queued'\n AND l.archived_flag = FALSE\n ORDER BY l.updated_at ASC\n LIMIT 1\n FOR UPDATE OF l SKIP LOCKED\n),\nadvanced AS (\n UPDATE bridge.leads\n SET marketing_status = 'Sending',\n updated_at = NOW()\n WHERE lead_id IN (SELECT lead_id FROM picked)\n RETURNING lead_id\n)\nSELECT p.* FROM picked p;",
"options": {}
},
"id": "node_pick_lead",
"name": "Pick lead + claim",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
460,
400
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"method": "POST",
"url": "=https://api.telegram.org/bot{{$env.BRIDGE_MARKETING_BOT_TOKEN}}/sendMessage",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json; charset=utf-8"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={ \"chat_id\": {{$env.BRIDGE_ADMIN_TELEGRAM_CHAT_ID}}, \"text\": \"\\ud83d\\udce7 Outreach in motion\\n\\n Prospect: {{ $('Pick lead + claim').first().json.business_name }}\\n City: {{ $('Pick lead + claim').first().json.city }}\\n Demo: {{ $('Pick lead + claim').first().json.demo_preview_url }}\\n\\nWriting the email now. Short, honest, no hype.\\n\\u2014 Marketing\" }",
"options": {
"timeout": 15000,
"response": {
"response": {
"neverError": true
}
}
}
},
"id": "node_mkt_start",
"name": "Marketing bot: starting",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
680,
400
]
},
{
"parameters": {
"jsCode": "const lead = $('Pick lead + claim').first().json;\nif (!lead || !lead.lead_id) {\n return { json: { __no_work__: true, reason: 'no lead ready for marketing' } };\n}\nconst body = $('Trigger').first().json.body || {};\n\nconst leadEmail = (lead.email && lead.email.trim()) ? lead.email.trim() : null;\nconst hasValidEmail = !!leadEmail;\nconst dry_run = (body.dry_run === true) || !hasValidEmail;\nconst adminEmail = $env.BRIDGE_ADMIN_EMAIL || 'bridge.digital.solution@gmail.com';\nconst to_email = dry_run ? adminEmail : leadEmail;\n\n// Prompt: generate a short, honest B2B cold email. HAIKU-sized.\nconst sys = \"You write short UK B2B outreach emails for Bridge Digital \"\n + \"Solution. Every email: (1) opens warmly addressing the business owner, \"\n + \"(2) explains we built a no-commitment demo website based on their \"\n + \"public info, (3) includes the demo URL, (4) invites them to reply by \"\n + \"email or WhatsApp/phone, (5) ends with a polite one-line opt-out. No \"\n + \"hype, no promises about rank/traffic, no fabricated testimonials. \"\n + \"Return ONLY a single JSON object {\\\"subject\\\":\\\"<=70 chars\\\",\"\n + \"\\\"body\\\":\\\"plain-text body, 100-220 words, uses \\\\n for line breaks\\\"}. \"\n + \"No markdown, no code fences.\";\n\nconst userFacts = \"BUSINESS: \" + lead.business_name\n + \" | CATEGORY: \" + (lead.category || 'service')\n + \" | CITY: \" + (lead.city || '')\n + \" | CURRENT_WEBSITE: \" + (lead.website_url_found || 'none')\n + \" | RATING: \" + (lead.reviews_rating ?? 'null')\n + \" | REVIEWS: \" + (lead.reviews_count ?? 0)\n + \" | DEMO_URL: \" + (lead.demo_preview_url || 'none')\n + \" | SENDER_NAME: Gelson at Bridge Digital Solution\"\n + \" | REPLY_TO: bridge.digital.solution@gmail.com\"\n + \" | WHATSAPP: +44 7345 787028\";\n\nreturn { json: {\n ...lead,\n to_email,\n dry_run,\n hasValidEmail,\n llm_request: {\n message: sys + \" FACTS: \" + userFacts,\n model: 'HAIKU',\n session_id: 'bridge-email-' + lead.lead_id,\n }\n}};",
"mode": "runOnceForAllItems"
},
"id": "node_build_email_req",
"name": "Build email request",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
900,
400
]
},
{
"parameters": {
"method": "POST",
"url": "https://super-agent-production.up.railway.app/chat/direct",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "X-Token",
"value": "={{$env.SUPER_AGENT_PASSWORD}}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify($json.llm_request) }}",
"options": {
"timeout": 60000,
"response": {
"response": {
"neverError": true
}
}
}
},
"id": "node_llm",
"name": "super-agent compose",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1120,
400
]
},
{
"parameters": {
"jsCode": "// Parse LLM output, compute expense, prepare Gmail send payload.\nconst prev = $('Build email request').first().json;\nconst api = $json;\n\nlet parsed = null;\nlet parseError = null;\ntry {\n const raw = (api.response || '').trim()\n .replace(/^```json\\s*/, '').replace(/```$/, '').trim();\n parsed = JSON.parse(raw);\n} catch (e) { parseError = e.message; }\n\n// Fallback \u2014 honest, facts-only body.\nfunction fallback() {\n const biz = prev.business_name || 'your business';\n const demo = prev.demo_preview_url || '';\n const city = prev.city || '';\n const subject = `Demo website for ${biz}`;\n const body = [\n `Hi ${biz} team,`,\n ``,\n `I put together a quick demo website based on the public info about ${biz}`,\n city ? `in ${city}. No commitment \u2014 just something to look at:` : `. No commitment \u2014 just something to look at:`,\n demo || '(preview pending)',\n ``,\n `If it looks useful, reply to this email or ping me on WhatsApp at +44 7345 787028`,\n `and I'll walk you through pricing and timelines.`,\n ``,\n `If not, no worries \u2014 reply with \"no thanks\" and I won't follow up.`,\n ``,\n `Gelson`,\n `Bridge Digital Solution`,\n `bridge.digital.solution@gmail.com`,\n ].join('\\n');\n return { subject, body, __fallback__: true, __fallback_reason__: parseError || 'empty' };\n}\n\nconst email = parsed || fallback();\nif (prev.dry_run) {\n email.subject = '[DRY-RUN] ' + email.subject;\n email.body = '[DRY-RUN \u2014 would have gone to ' + (prev.hasValidEmail ? prev.email : '(no lead email)') + ']\\n\\n' + email.body;\n}\n\n// Rough token-based cost estimate (Anthropic HAIKU 2026-04: $0.80/M in, $4/M out)\nconst chars_in = (JSON.stringify(prev.llm_request) || '').length;\nconst chars_out = (api.response || '').length;\nconst input_tokens = Math.ceil(chars_in / 4);\nconst output_tokens = Math.ceil(chars_out / 4);\nconst llm_cost_usd = Number(((input_tokens * 0.80 + output_tokens * 4) / 1_000_000).toFixed(4));\n\nreturn { json: {\n ...prev,\n subject: email.subject,\n body: email.body,\n llm_cost_usd,\n llm_meta: { input_tokens, output_tokens, model: api.model_used || 'HAIKU', fallback: email.__fallback__ === true }\n}};",
"mode": "runOnceForAllItems"
},
"id": "node_parse_email",
"name": "Parse email + compute cost",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1340,
400
]
},
{
"parameters": {
"operation": "send",
"sendTo": "={{ $json.to_email }}",
"subject": "={{ $json.subject }}",
"emailType": "text",
"message": "={{ $json.body }}",
"options": {
"appendAttribution": false
}
},
"id": "node_gmail",
"name": "Gmail: send",
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [
1560,
400
],
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"continueOnFail": true
},
{
"parameters": {
"jsCode": "// Gmail send node returns the message object with id + threadId on success.\n// With neverError true, a failure lands here too \u2014 detect by checking id.\nconst prev = $('Parse email + compute cost').first().json;\nconst resp = $json || {};\n\nconst send_ok = !!(resp.id || resp.messageId);\nconst provider_message_id = resp.id || resp.messageId || null;\nconst thread_id = resp.threadId || null;\n\nreturn { json: { ...prev, send_ok, provider_message_id, thread_id, send_raw: resp } };",
"mode": "runOnceForAllItems"
},
"id": "node_parse_send",
"name": "Parse Gmail send",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1780,
400
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "WITH p AS (SELECT $1::jsonb AS d)\nINSERT INTO bridge.outreach_messages\n (lead_id, channel, direction, template_key, subject, body,\n provider_message_id, thread_id, delivery_status, sent_at)\nSELECT\n (d->>'lead_id')::uuid,\n 'email',\n 'outbound',\n COALESCE(d->>'template_key', 'initial_email_v1'),\n d->>'subject',\n d->>'body',\n d->>'provider_message_id',\n d->>'thread_id',\n CASE WHEN (d->>'send_ok')::boolean THEN 'sent' ELSE 'failed' END,\n CASE WHEN (d->>'send_ok')::boolean THEN NOW() ELSE NULL END\nFROM p\nRETURNING message_id, delivery_status;",
"options": {
"queryReplacement": "={{ JSON.stringify({lead_id: $json.lead_id, template_key: 'initial_email_v1', subject: $json.subject, body: $json.body, provider_message_id: $json.provider_message_id, thread_id: $json.thread_id, send_ok: $json.send_ok}) }}"
}
},
"id": "node_insert_msg",
"name": "Insert outreach_message",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
2000,
400
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "executeQuery",
"query": "WITH p AS (SELECT $1::jsonb AS d)\nUPDATE bridge.leads\nSET marketing_status = CASE\n WHEN (d->>'send_ok')::boolean THEN 'Awaiting Reply'\n ELSE 'Queued' -- retry-able\n END,\n updated_at = NOW()\nFROM p\nWHERE lead_id = (d->>'lead_id')::uuid\nRETURNING lead_id, marketing_status, business_name;",
"options": {
"queryReplacement": "={{ JSON.stringify({lead_id: $('Parse Gmail send').first().json.lead_id, send_ok: $('Parse Gmail send').first().json.send_ok}) }}"
}
},
"id": "node_update_lead",
"name": "Lead \u2192 Awaiting Reply (gated)",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
2220,
400
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "executeQuery",
"query": "WITH p AS (SELECT $1::jsonb AS d)\nINSERT INTO bridge.expenses\n (workflow_name, lead_id, category, vendor, description, units, unit_label, amount_usd, details_json)\nSELECT\n 'marketing', (d->>'lead_id')::uuid, 'llm_tokens', 'anthropic_haiku',\n 'HAIKU outreach email generation for '||(d->>'business_name'),\n 1, 'calls',\n CAST(d->>'llm_cost_usd' AS NUMERIC(10,4)),\n (d->'llm_meta')::jsonb\nFROM p\nRETURNING expense_id, amount_usd;",
"options": {
"queryReplacement": "={{ JSON.stringify({lead_id: $('Parse Gmail send').first().json.lead_id, business_name: $('Parse Gmail send').first().json.business_name, llm_cost_usd: $('Parse Gmail send').first().json.llm_cost_usd, llm_meta: $('Parse Gmail send').first().json.llm_meta}) }}"
}
},
"id": "node_log_expense",
"name": "Log LLM expense",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
2440,
400
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "executeQuery",
"query": "WITH p AS (SELECT $1::jsonb AS d)\nINSERT INTO bridge.workflow_events\n (lead_id, workflow_name, event_type, old_status, new_status, details_json)\nSELECT\n (d->>'lead_id')::uuid, 'marketing',\n CASE WHEN (d->>'send_ok')::boolean THEN 'email_sent' ELSE 'email_failed' END,\n 'Queued',\n CASE WHEN (d->>'send_ok')::boolean THEN 'Awaiting Reply' ELSE 'Queued' END,\n jsonb_build_object(\n 'channel', 'email',\n 'subject', d->>'subject',\n 'to_email', d->>'to_email',\n 'dry_run', (d->>'dry_run')::boolean,\n 'provider_message_id', d->>'provider_message_id',\n 'thread_id', d->>'thread_id',\n 'llm_cost_usd', d->>'llm_cost_usd'\n )\nFROM p\nRETURNING event_id;",
"options": {
"queryReplacement": "={{ JSON.stringify({lead_id: $('Parse Gmail send').first().json.lead_id, subject: $('Parse Gmail send').first().json.subject, to_email: $('Parse Gmail send').first().json.to_email, dry_run: $('Parse Gmail send').first().json.dry_run, provider_message_id: $('Parse Gmail send').first().json.provider_message_id, thread_id: $('Parse Gmail send').first().json.thread_id, send_ok: $('Parse Gmail send').first().json.send_ok, llm_cost_usd: $('Parse Gmail send').first().json.llm_cost_usd}) }}"
}
},
"id": "node_log_event",
"name": "Log workflow_event",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
2660,
400
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"method": "POST",
"url": "=https://api.telegram.org/bot{{$env.BRIDGE_MARKETING_BOT_TOKEN}}/sendMessage",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json; charset=utf-8"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={ \"chat_id\": {{$env.BRIDGE_ADMIN_TELEGRAM_CHAT_ID}}, \"text\": \"{{ $('Parse Gmail send').first().json.send_ok ? '\\u2709\\ufe0f Email sent' : '\\u26a0\\ufe0f Send failed' }}{{ $('Parse Gmail send').first().json.dry_run ? ' \\u00b7 DRY-RUN' : '' }}\\n\\n Prospect: {{ $('Parse Gmail send').first().json.business_name }}\\n To: {{ $('Parse Gmail send').first().json.to_email }}\\n Subject: {{ $('Parse Gmail send').first().json.subject }}\\n Thread: {{ $('Parse Gmail send').first().json.thread_id || 'n/a' }}\\n\\n Status: {{ $('Parse Gmail send').first().json.send_ok ? 'Awaiting Reply' : 'Queued (retry)' }}\\n Next: watching for reply \\u00b7 follow-up in 24h if silent\\n\\n\\u2014 Marketing\" }",
"options": {
"timeout": 15000,
"response": {
"response": {
"neverError": true
}
}
}
},
"id": "node_mkt_done",
"name": "Marketing bot: done",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2880,
300
]
},
{
"parameters": {
"method": "POST",
"url": "=https://api.telegram.org/bot{{$env.BRIDGE_FINANCE_BOT_TOKEN}}/sendMessage",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json; charset=utf-8"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={ \"chat_id\": {{$env.BRIDGE_ADMIN_TELEGRAM_CHAT_ID}}, \"text\": \"\\ud83e\\uddfe Outreach email billed\\n\\n Lead: {{ $('Parse Gmail send').first().json.business_name }}\\n Model: HAIKU ({{ $('Parse Gmail send').first().json.llm_meta.input_tokens }} tok in / {{ $('Parse Gmail send').first().json.llm_meta.output_tokens }} tok out)\\n Cost: ${{ $('Parse Gmail send').first().json.llm_cost_usd }}\\n\\nLogged to bridge.expenses.\\n\\u2014 Accounts\" }",
"options": {
"timeout": 15000,
"response": {
"response": {
"neverError": true
}
}
}
},
"id": "node_fin",
"name": "Finance bot: cost",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2880,
500
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={ \"status\": \"ok\", \"lead_id\": {{ JSON.stringify($('Parse Gmail send').first().json.lead_id) }}, \"to_email\": {{ JSON.stringify($('Parse Gmail send').first().json.to_email) }}, \"dry_run\": {{ $('Parse Gmail send').first().json.dry_run }}, \"subject\": {{ JSON.stringify($('Parse Gmail send').first().json.subject) }}, \"send_ok\": {{ $('Parse Gmail send').first().json.send_ok }}, \"thread_id\": {{ JSON.stringify($('Parse Gmail send').first().json.thread_id) }}, \"llm_cost_usd\": {{ $('Parse Gmail send').first().json.llm_cost_usd }} }",
"options": {}
},
"id": "node_respond",
"name": "Respond OK",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
3100,
400
]
}
],
"connections": {
"Trigger": {
"main": [
[
{
"node": "Pick lead + claim",
"type": "main",
"index": 0
}
]
]
},
"Pick lead + claim": {
"main": [
[
{
"node": "Marketing bot: starting",
"type": "main",
"index": 0
}
]
]
},
"Marketing bot: starting": {
"main": [
[
{
"node": "Build email request",
"type": "main",
"index": 0
}
]
]
},
"Build email request": {
"main": [
[
{
"node": "super-agent compose",
"type": "main",
"index": 0
}
]
]
},
"super-agent compose": {
"main": [
[
{
"node": "Parse email + compute cost",
"type": "main",
"index": 0
}
]
]
},
"Parse email + compute cost": {
"main": [
[
{
"node": "Gmail: send",
"type": "main",
"index": 0
}
]
]
},
"Gmail: send": {
"main": [
[
{
"node": "Parse Gmail send",
"type": "main",
"index": 0
}
]
]
},
"Parse Gmail send": {
"main": [
[
{
"node": "Insert outreach_message",
"type": "main",
"index": 0
}
]
]
},
"Insert outreach_message": {
"main": [
[
{
"node": "Lead \u2192 Awaiting Reply (gated)",
"type": "main",
"index": 0
}
]
]
},
"Lead \u2192 Awaiting Reply (gated)": {
"main": [
[
{
"node": "Log LLM expense",
"type": "main",
"index": 0
}
]
]
},
"Log LLM expense": {
"main": [
[
{
"node": "Log workflow_event",
"type": "main",
"index": 0
}
]
]
},
"Log workflow_event": {
"main": [
[
{
"node": "Marketing bot: done",
"type": "main",
"index": 0
}
]
]
},
"Marketing bot: done": {
"main": [
[
{
"node": "Finance bot: cost",
"type": "main",
"index": 0
}
]
]
},
"Finance bot: cost": {
"main": [
[
{
"node": "Respond OK",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
}
}
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.
gmailOAuth2postgres
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
bridge_marketing. Uses postgres, httpRequest, gmail. Webhook trigger; 15 nodes.
Source: https://github.com/gelson12/super-agent/blob/cd01d300d2515b93a620e878f89f3c6e5e8b1b73/n8n/bridge_marketing.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.
This workflow captures incoming leads from a webhook, enriches them using AI, and automates the entire sales pipeline. It validates data, scores leads as hot, warm, or cold, assigns them to sales reps
This workflow was created by Varritech Technologies, a cutting-edge software agency that helps founders and operators go from idea to production 5× faster using AI. Based in New York City, we speciali
COMPLEXITY-004: Multiple unrelated concerns in one workflow. Uses gmail, slack, postgres, googleDrive. Webhook trigger; 15 nodes.
Jigsaw API key for image processing, I use this as a gatekeeper/second pair of eyes. LINK to their website https://jigsawstack.com/ SECOND A postgress DATABASE (I use Supabase) LlamaCloud for the pars
W1 - IN WhatsApp Adapter (Secure + Fast ACK). Uses postgres, redis, httpRequest. Webhook trigger; 50 nodes.