<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: AgoraIntelligence</title>
    <description>The latest articles on DEV Community by AgoraIntelligence (@agoraintelligence).</description>
    <link>https://dev.to/agoraintelligence</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4025979%2F4d7dd20b-201f-4f43-9114-29ee73d69631.png</url>
      <title>DEV Community: AgoraIntelligence</title>
      <link>https://dev.to/agoraintelligence</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/agoraintelligence"/>
    <language>en</language>
    <item>
      <title>One Piece of Content, Four Platforms, One Webhook Call — Content Repurposing with n8n and GPT-4o-mini</title>
      <dc:creator>AgoraIntelligence</dc:creator>
      <pubDate>Sun, 26 Jul 2026 17:33:02 +0000</pubDate>
      <link>https://dev.to/agoraintelligence/one-piece-of-content-four-platforms-one-webhook-call-content-repurposing-with-n8n-and-46b9</link>
      <guid>https://dev.to/agoraintelligence/one-piece-of-content-four-platforms-one-webhook-call-content-repurposing-with-n8n-and-46b9</guid>
      <description>&lt;p&gt;One Piece of Content, Four Platforms, One Webhook Call — Content Repurposing with n8n and GPT-4o-mini&lt;/p&gt;

&lt;p&gt;The biggest bottleneck in content marketing isn't ideas. It's adaptation.&lt;/p&gt;

&lt;p&gt;You write a good LinkedIn post. Then you need to turn it into a Twitter thread. Then a Telegram message for your channel. Then a newsletter intro. Each platform has different tone, format, and length requirements. By the time you've adapted the same idea four times, you've spent an hour on distribution instead of creation.&lt;/p&gt;

&lt;p&gt;This workflow collapses that hour into a single webhook call.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Does
&lt;/h2&gt;

&lt;p&gt;Send a topic or article URL to an n8n webhook. Get back:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LinkedIn post&lt;/strong&gt; — professional tone, 150-300 words, hook + insight + CTA&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Twitter/X thread&lt;/strong&gt; — 3 tweets, punchy, ready to paste&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Telegram message&lt;/strong&gt; — casual, with emojis, for your channel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Newsletter intro&lt;/strong&gt; — subject line + opening paragraph, 100-150 words&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;LinkedIn and Telegram publish automatically. Twitter and newsletter content are returned ready-to-paste (X API requires a paid subscription — we skip that dependency).&lt;/p&gt;

&lt;p&gt;Optionally: a DALL-E 3 image matching the content, 1024x1024.&lt;/p&gt;

&lt;p&gt;Everything gets logged to Google Sheets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why One Webhook Call
&lt;/h2&gt;

&lt;p&gt;The naive approach uses four separate OpenAI calls — one per platform. This costs 4× more tokens and introduces 4× more failure points.&lt;/p&gt;

&lt;p&gt;The better approach: one structured JSON output call that returns all four variants simultaneously.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
You are a content strategist. Generate platform-optimized content for this topic.

Topic: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
Brand voice: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;brand_voice&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
Language: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
Industry: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;industry&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;

Return a JSON object with exactly these keys:
{
  "linkedin": "full LinkedIn post text",
  "twitter_thread": ["tweet 1", "tweet 2", "tweet 3"],
  "telegram": "Telegram message with emojis",
  "newsletter_subject": "Email subject line",
  "newsletter_intro": "Opening paragraph for newsletter"
}

Rules:
- LinkedIn: professional, add 3 relevant hashtags at the end
- Twitter: each tweet max 280 chars, thread format with numbering
- Telegram: conversational, 2-4 emojis, max 300 chars
- Newsletter: subject under 50 chars, intro under 150 words
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;response_format: { type: "json_object" }&lt;/code&gt; in the API call, you get clean structured output every time — no regex parsing, no format failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Workflow Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Webhook (POST) → Parse input (topic/URL)
  → [Optional] Fetch URL → Extract article text
  → Code: Build structured prompt
  → OpenAI: Generate all 4 variants (JSON mode)
  → [Optional] DALL-E 3: Generate image
  → Parallel branches:
      → LinkedIn: Publish post via API
      → Telegram: Send via bot
      → Sheets: Log everything
  → Respond: Return JSON with all content + publish URLs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parallel branches mean LinkedIn and Telegram publish simultaneously — no sequential wait.&lt;/p&gt;

&lt;h2&gt;
  
  
  Triggering It
&lt;/h2&gt;

&lt;p&gt;The webhook accepts a simple POST:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://your-n8n.com/webhook/repurpose &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "topic": "Why most automation projects fail in the first 90 days",
    "url": null,
    "generate_image": false
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or with an article URL to repurpose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://your-n8n.com/webhook/repurpose &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "topic": null,
    "url": "https://example.com/article-to-repurpose",
    "generate_image": true
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response includes all generated content plus publication status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"linkedin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The #1 reason automation projects fail..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"twitter_thread"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"1/ Most companies start automating the wrong things..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2/..."&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"telegram"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"🤖 Hot take: most automation projects die in month 2..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"newsletter_subject"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Why your automation will fail"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"newsletter_intro"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Last quarter we analyzed..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"image_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://oaidalleapiprodscus.blob.core.windows.net/..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"linkedin_post_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:li:share:123456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"telegram_message_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"logged_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-26T14:32:00Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The CONFIG Node
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CONFIG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;openai_credential&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-openai-credential-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// LinkedIn&lt;/span&gt;
  &lt;span class="na"&gt;linkedin_access_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YOUR_LINKEDIN_TOKEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;linkedin_person_urn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;urn:li:person:YOUR_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// Telegram&lt;/span&gt;
  &lt;span class="na"&gt;telegram_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YOUR_BOT_TOKEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;telegram_chat_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@yourchannel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// Google Sheets&lt;/span&gt;
  &lt;span class="na"&gt;sheet_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YOUR_SPREADSHEET_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;gcp_credential&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-service-account-credential-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// Content settings&lt;/span&gt;
  &lt;span class="na"&gt;brand_voice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;professional but direct, no corporate jargon&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;English&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;industry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;B2B SaaS / automation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;default_hashtags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#automation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#n8n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#AI&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

  &lt;span class="c1"&gt;// Image generation (costs ~$0.04 per image with DALL-E 3)&lt;/span&gt;
  &lt;span class="na"&gt;generate_images_by_default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  LinkedIn API Setup
&lt;/h2&gt;

&lt;p&gt;LinkedIn's API requires OAuth with &lt;code&gt;w_member_social&lt;/code&gt; scope. The flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a LinkedIn App at &lt;a href="https://www.linkedin.com/developers/" rel="noopener noreferrer"&gt;linkedin.com/developers&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;w_member_social&lt;/code&gt; and &lt;code&gt;r_liteprofile&lt;/code&gt; permissions&lt;/li&gt;
&lt;li&gt;Generate an access token (valid 60 days — set a reminder to refresh)&lt;/li&gt;
&lt;li&gt;Get your Person URN: &lt;code&gt;GET https://api.linkedin.com/v2/userinfo&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Publishing a post:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST https://api.linkedin.com/v2/ugcPosts
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "author": "urn:li:person:YOUR_ID",
  "lifecycleState": "PUBLISHED",
  "specificContent": {
    "com.linkedin.ugc.ShareContent": {
      "shareCommentary": { "text": "{{linkedin_content}}" },
      "shareMediaCategory": "NONE"
    }
  },
  "visibility": { "com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC" }
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;LinkedIn token expiry gotcha:&lt;/strong&gt; tokens expire after 60 days. Either implement the OAuth refresh flow in n8n (complex) or set a calendar reminder to regenerate it. Most people do the calendar reminder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost Per Run
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GPT-4o-mini (all 4 variants)&lt;/td&gt;
&lt;td&gt;~$0.003&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DALL-E 3 image (optional)&lt;/td&gt;
&lt;td&gt;~$0.040&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;n8n execution&lt;/td&gt;
&lt;td&gt;$0 (self-hosted)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total without image&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$0.003&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total with image&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$0.043&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At $0.003 per run, you'd need 333 daily repurposing runs to spend $1/day. For most content creators publishing 1-3 times per day, the monthly cost is under $0.30.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Is Good For (and What It Isn't)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Good for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repurposing blog posts and articles across channels&lt;/li&gt;
&lt;li&gt;Consistent weekly content without the copy-paste grind&lt;/li&gt;
&lt;li&gt;Teams where one person creates and others distribute&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Not good for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Content requiring deep research or original reporting&lt;/li&gt;
&lt;li&gt;Platforms requiring native video/carousel formats&lt;/li&gt;
&lt;li&gt;Situations where brand voice is extremely specific — AI needs calibration time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The quality improves dramatically when you fill in the &lt;code&gt;brand_voice&lt;/code&gt; and &lt;code&gt;industry&lt;/code&gt; fields with specifics. "Professional B2B SaaS company that sells to CTOs, writes like Paul Graham, no buzzwords" produces noticeably better output than "professional."&lt;/p&gt;

&lt;h2&gt;
  
  
  Get the Workflow
&lt;/h2&gt;

&lt;p&gt;The complete 14-node workflow is available at &lt;a href="https://n8nmarkets.com" rel="noopener noreferrer"&gt;n8nmarkets.com&lt;/a&gt;. Search "Social Media Content Repurposer".&lt;/p&gt;

&lt;p&gt;Includes: workflow JSON, Sheets logging template, LinkedIn OAuth setup guide, and curl commands to test every branch before connecting real social accounts.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What's your current content distribution process? Curious whether people are using schedulers like Buffer/Hootsuite alongside this kind of automation, or replacing them entirely.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>automation</category>
      <category>contentmarketing</category>
      <category>ai</category>
    </item>
    <item>
      <title>Stop Copying Meeting Details from Email to Calendar — Automate It with n8n and GPT-4o-mini</title>
      <dc:creator>AgoraIntelligence</dc:creator>
      <pubDate>Sun, 26 Jul 2026 16:51:27 +0000</pubDate>
      <link>https://dev.to/agoraintelligence/stop-copying-meeting-details-from-email-to-calendar-automate-it-with-n8n-and-gpt-4o-mini-1ndo</link>
      <guid>https://dev.to/agoraintelligence/stop-copying-meeting-details-from-email-to-calendar-automate-it-with-n8n-and-gpt-4o-mini-1ndo</guid>
      <description>&lt;p&gt;Stop Copying Meeting Details from Email to Calendar — Automate It with n8n and GPT-4o-mini&lt;/p&gt;

&lt;p&gt;You receive a Calendly confirmation. Or a Zoom invite. Or an email that says "let's meet Tuesday at 3pm." &lt;/p&gt;

&lt;p&gt;Every time, you open your calendar, create an event, copy the link, paste the details. Then you spend 5 minutes skimming the email thread trying to remember what the meeting is actually about before it starts.&lt;/p&gt;

&lt;p&gt;This workflow eliminates both steps: it reads your Gmail every 15 minutes, detects meeting-related emails, creates the calendar event automatically, and sends you an AI-generated prep brief to Slack before you even open your inbox.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Detects
&lt;/h2&gt;

&lt;p&gt;The workflow uses GPT-4o-mini to classify incoming emails. It catches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zoom / Google Meet / Teams confirmations&lt;/strong&gt; — the standard automated confirmations from each platform&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calendly booking confirmations&lt;/strong&gt; — "Your meeting with X has been confirmed"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Informal meeting requests&lt;/strong&gt; — "Let's catch up Wednesday at 2pm" in a regular email&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flight and hotel reservations&lt;/strong&gt; — date + time + location = calendar event&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restaurant reservations&lt;/strong&gt; — same pattern&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calendar invitations&lt;/strong&gt; forwarded via email&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It ignores newsletters, marketing emails, no-reply senders, and promotional content. You configure the filter list once in the CONFIG node.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Output
&lt;/h2&gt;

&lt;p&gt;For each detected meeting, the workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Creates a Google Calendar event&lt;/strong&gt; with title, date, time, duration, location, and video link pre-filled&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generates a meeting brief&lt;/strong&gt; via AI — context summary, 3-4 talking points, 2-3 questions to prepare&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sends the brief to Slack&lt;/strong&gt; with a direct link to the calendar event&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logs the entry to Google Sheets&lt;/strong&gt; — date, email subject, event created, AI brief status&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The Slack message looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;📅 Meeting Created: Discovery Call with Acme Corp
📍 Tuesday 29 Jul, 15:00 — 16:00 (Google Meet)
🔗 meet.google.com/abc-defg-hij

📋 Context: Acme Corp is exploring automation solutions for their sales team. 
They mentioned a current Zapier setup they want to move off of.

💡 Talking points:
• Their current Zapier spend and what's breaking
• Which workflows they need first
• Their technical setup (CRM, email tool, data sources)

❓ Questions to prepare:
• Who makes the final decision on tools?
• What's the timeline for implementation?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Workflow Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Schedule (every 15 min)
  → Gmail: Read unread emails (last 30 min)
  → Filter: Remove newsletters/no-reply
  → OpenAI: Classify — is this a meeting? (YES/NO + extracted details)
  → IF YES:
      → Google Calendar: Create event
      → OpenAI: Generate prep brief
      → Slack: Send brief + calendar link
      → Gmail: Mark as read + label "calendar-added"
      → Sheets: Log entry
  → IF NO: Skip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The AI Extraction Prompt
&lt;/h2&gt;

&lt;p&gt;This is the most important node — the quality of the calendar event depends on how well you extract from the email:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;You&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;scheduling&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;assistant.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Extract&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;meeting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;details&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;email.&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;Email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;subject:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="err"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;body:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="err"&gt;body&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Sender:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="err"&gt;from&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Today's&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;date:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="err"&gt;today&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;Return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;JSON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;with:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"is_meeting"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Meeting title (descriptive, not the email subject)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YYYY-MM-DD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HH:MM"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"duration_minutes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timezone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Europe/Madrid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"video_link"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://... or null"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"physical address or null"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"attendees"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"email1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"email2"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2-3 sentence summary of what this meeting is about"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0-1.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;If&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is_meeting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;only&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"is_meeting"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Only&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;JSON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;object,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;no&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;other&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;text.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set a confidence threshold (e.g. 0.7) — below that, skip the event creation and log it for manual review instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gmail Deduplication
&lt;/h2&gt;

&lt;p&gt;The biggest practical problem with email-to-calendar workflows: you don't want the same meeting created twice if you receive multiple confirmation emails (original invite + reminder + updated time).&lt;/p&gt;

&lt;p&gt;Two solutions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option A — Label-based:&lt;/strong&gt; After processing an email, add a Gmail label &lt;code&gt;calendar-added&lt;/code&gt;. The next run filters out emails with this label.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option B — Sheets-based:&lt;/strong&gt; Log the email Message-ID to a Sheets column and check for duplicates before creating the event.&lt;/p&gt;

&lt;p&gt;Label-based is simpler and more reliable. Add a Gmail node at the end of the success branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Gmail → Modify Message → Add Label: calendar-added
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the read step, filter: &lt;code&gt;is:unread -label:calendar-added newer_than:1h&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling Timezone Ambiguity
&lt;/h2&gt;

&lt;p&gt;"Meet Tuesday at 3pm" — 3pm where?&lt;/p&gt;

&lt;p&gt;The prep brief prompt includes your configured timezone. If the email doesn't specify, the workflow defaults to your timezone. Add a note to the Slack message when timezone was assumed rather than explicit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timezoneNote&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;detectedTimezone&lt;/span&gt; 
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`🌍 Timezone: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;detectedTimezone&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; 
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`⚠️ Timezone assumed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;default_timezone&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Cost
&lt;/h2&gt;

&lt;p&gt;At 50 emails/day processed through GPT-4o-mini:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Classification call: ~200 tokens per email → $0.003/day&lt;/li&gt;
&lt;li&gt;Brief generation (meetings only, ~20% of emails): ~500 tokens → $0.0015/day&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Total: ~$0.0045/day → ~$1.35/month&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a professional who books 5-10 meetings per week, the time saved (5+ minutes per meeting) pays back the cost in the first hour of the first day.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CONFIG Node
&lt;/h2&gt;

&lt;p&gt;One node controls everything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CONFIG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;gmail_credential&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-gmail-oauth-credential-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;calendar_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;primary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// or specific calendar ID&lt;/span&gt;
  &lt;span class="na"&gt;slack_webhook&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://hooks.slack.com/services/...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;sheets_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-spreadsheet-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;openai_credential&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-openai-credential-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// Your profile (personalizes the brief)&lt;/span&gt;
  &lt;span class="na"&gt;your_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Igor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;your_role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Automation consultant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;your_company&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Agora Intelligence&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// Filter: emails from these domains are always skipped&lt;/span&gt;
  &lt;span class="na"&gt;skip_domains&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mailchimp.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;klaviyo.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;newsletter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;skip_senders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;noreply&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no-reply&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;donotreply&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

  &lt;span class="c1"&gt;// Minimum confidence to create a calendar event (0-1)&lt;/span&gt;
  &lt;span class="na"&gt;confidence_threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// Default timezone when not specified in email&lt;/span&gt;
  &lt;span class="na"&gt;default_timezone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Europe/Madrid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Get the Complete Workflow
&lt;/h2&gt;

&lt;p&gt;The full 18-node workflow — with Gmail deduplication, confidence thresholds, timezone handling, Slack formatting, and Sheets logging — is available at &lt;a href="https://n8nmarkets.com" rel="noopener noreferrer"&gt;n8nmarkets.com&lt;/a&gt;. Search "Email to Calendar AI Meeting Prep".&lt;/p&gt;

&lt;p&gt;Includes the workflow JSON, Google Sheets template, and setup guide for Gmail OAuth (the most common friction point).&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What meeting-related emails do you receive most often? The workflow handles standard confirmations well, but I'm curious what edge cases people run into — forwarded invites, multi-timezone teams, recurring meetings with agenda updates.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>automation</category>
      <category>productivity</category>
      <category>ai</category>
    </item>
    <item>
      <title>Extract Business Leads from Google Maps with Emails — Automated Pipeline with n8n and Apify</title>
      <dc:creator>AgoraIntelligence</dc:creator>
      <pubDate>Sun, 26 Jul 2026 16:50:27 +0000</pubDate>
      <link>https://dev.to/agoraintelligence/extract-business-leads-from-google-maps-with-emails-automated-pipeline-with-n8n-and-apify-3nno</link>
      <guid>https://dev.to/agoraintelligence/extract-business-leads-from-google-maps-with-emails-automated-pipeline-with-n8n-and-apify-3nno</guid>
      <description>&lt;p&gt;Extract Business Leads from Google Maps with Emails — Automated Pipeline with n8n and Apify&lt;/p&gt;

&lt;p&gt;Cold outreach only works if you have good data. Most lead lists are outdated, incomplete, or scraped from directories that haven't been maintained in years.&lt;/p&gt;

&lt;p&gt;Google Maps is the opposite: it's the most maintained business directory on the planet, updated in real-time by business owners and users. This tutorial shows how to build an automated pipeline that extracts business leads from Google Maps — complete with contact emails scraped from their websites — and delivers them as a clean spreadsheet.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Pipeline Extracts
&lt;/h2&gt;

&lt;p&gt;For each business in your search results:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Business name, category, address&lt;/li&gt;
&lt;li&gt;Phone number&lt;/li&gt;
&lt;li&gt;Website URL&lt;/li&gt;
&lt;li&gt;Google rating and review count&lt;/li&gt;
&lt;li&gt;Opening hours&lt;/li&gt;
&lt;li&gt;Contact emails (scraped from the business website)&lt;/li&gt;
&lt;li&gt;Direct Google Maps URL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A search for "dentistas Madrid" returns 20-50 businesses in one run. A search for "plumbers London" or "restaurants Barcelona" works identically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture: Two-Phase Extraction
&lt;/h2&gt;

&lt;p&gt;The extraction runs in two phases to handle Google Maps' dynamic JavaScript rendering:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 1 — Search results scraping&lt;/strong&gt;&lt;br&gt;
Navigate to &lt;code&gt;google.com/maps/search/[query]&lt;/code&gt;, scroll the results feed to load all listings, collect the URL of each place page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 2 — Detail page extraction&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Visit each place URL individually to extract the structured data (phone, address, website, rating). Then fetch the business website to find contact emails.&lt;/p&gt;

&lt;p&gt;This two-phase approach avoids the timeout issues that come from trying to extract everything in one pass on a dynamic-loading page.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Email Extraction Logic
&lt;/h2&gt;

&lt;p&gt;Emails are scraped from these pages on each business website (in order):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Homepage&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/contact&lt;/code&gt; or &lt;code&gt;/contacto&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/about&lt;/code&gt; or &lt;code&gt;/sobre-nosotros&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The scraper stops after finding 3 valid emails per domain. It filters out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generic/no-reply addresses (&lt;code&gt;noreply@&lt;/code&gt;, &lt;code&gt;postmaster@&lt;/code&gt;, etc.)&lt;/li&gt;
&lt;li&gt;System emails (&lt;code&gt;@sentry.io&lt;/code&gt;, &lt;code&gt;@amazonaws.com&lt;/code&gt;, etc.)&lt;/li&gt;
&lt;li&gt;Placeholder addresses (&lt;code&gt;@example.com&lt;/code&gt;, &lt;code&gt;@yourdomain.com&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;False positives from CSS/JS files (e.g. &lt;code&gt;background.png@&lt;/code&gt;)
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SKIP_DOMAINS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sentry.io&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;google.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;googleapis.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wixpress.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shopify.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;amazonaws.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cloudflare.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isValidEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lower&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SKIP_DOMAINS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.(&lt;/span&gt;&lt;span class="sr"&gt;png|jpg|svg|css|js&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;$/i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;noreply|no-reply|donotreply|postmaster&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Typical email hit rate: &lt;strong&gt;40-60% of businesses&lt;/strong&gt; have a public email on their website.&lt;/p&gt;
&lt;h2&gt;
  
  
  Option A: Build It Yourself (n8n + Playwright)
&lt;/h2&gt;

&lt;p&gt;If you're self-hosting n8n on a VPS, you can run the full pipeline yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Node structure:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Manual Trigger / Schedule
  → Code: Build search URL
  → Playwright: Scrape Maps search results
  → Split In Batches (1 at a time)
  → Playwright: Extract place details
  → HTTP Request: Fetch business website
  → Code: Extract emails with regex
  → Google Sheets: Append row
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key Playwright snippet for collecting place links:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;collectPlaceLinks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maxResults&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;collected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;noGrowthRounds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;collected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;maxResults&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;links&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="nf"&gt;$eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a[href*="/maps/place/"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;els&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;els&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;links&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;collected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="c1"&gt;// Scroll to load more&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;feed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div[role="feed"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scrollBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1800&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Stop if no new results after 3 scrolls&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;collected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;prevSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;noGrowthRounds&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;noGrowthRounds&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;collected&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maxResults&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Memory warning:&lt;/strong&gt; Don't run more than 1 concurrent browser instance. Google Maps detail pages are heavy. Process one place at a time (&lt;code&gt;maxConcurrency: 1&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Option B: Use the Apify Actor (No Infrastructure Needed)
&lt;/h2&gt;

&lt;p&gt;If you don't want to manage Playwright on your own infrastructure, the same pipeline is available as an Apify actor.&lt;/p&gt;

&lt;p&gt;Apify handles the browser infrastructure, proxies, and scaling. You provide the search parameters and get back a structured dataset.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Input:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dentistas"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Madrid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxResults"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"extractEmails"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"es"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt; (per business):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Clínica Dental López"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Calle Gran Vía 28, Madrid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"phone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"+34 91 123 4567"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"website"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://clinicadentallopez.es"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rating"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reviewCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;183&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Dentist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hours"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mon-Fri 9:00-20:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"emails"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"info@clinicadentallopez.es"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mapsUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.google.com/maps/place/..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pricing: &lt;strong&gt;$0.002 per result&lt;/strong&gt; (pay-per-use, no subscription). 50 leads = $0.10.&lt;/p&gt;

&lt;p&gt;The actor is &lt;a href="https://apify.com/agoradelmediterraneo/google-maps-business-enricher" rel="noopener noreferrer"&gt;Google Maps Business Enricher on Apify Store&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Google Sheets Output
&lt;/h2&gt;

&lt;p&gt;The pipeline writes directly to a Google Sheet with these columns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csvs"&gt;&lt;code&gt;&lt;span class="k"&gt;Name&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Address&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Phone&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Website&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Rating&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Reviews&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Category&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Hours&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Emails&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Maps&lt;/span&gt; &lt;span class="k"&gt;URL&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Extracted&lt;/span&gt; &lt;span class="k"&gt;At&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Filter by rating to only target well-reviewed businesses&lt;/li&gt;
&lt;li&gt;Filter by email presence for immediate outreach&lt;/li&gt;
&lt;li&gt;Sort by review count to find established businesses (not fly-by-night)&lt;/li&gt;
&lt;li&gt;Export to CSV for your email tool&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Use Cases
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Agency prospecting:&lt;/strong&gt; "Marketing agencies" + city → cold email pitch&lt;br&gt;&lt;br&gt;
&lt;strong&gt;B2B sales:&lt;/strong&gt; "Clinics" + region → pitch your SaaS&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Market research:&lt;/strong&gt; Count how many businesses in a category exist in a city&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Competitor mapping:&lt;/strong&gt; Find all competitors in your area with their ratings&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Supplier discovery:&lt;/strong&gt; Find local suppliers in a specific category  &lt;/p&gt;

&lt;h2&gt;
  
  
  Legal Considerations
&lt;/h2&gt;

&lt;p&gt;This scrapes publicly available information from Google Maps and business websites — the same data you'd see if you searched manually. Treat the output like any other public directory data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don't use it for spam&lt;/li&gt;
&lt;li&gt;Respect GDPR/CAN-SPAM — include unsubscribe options in any outreach&lt;/li&gt;
&lt;li&gt;Only contact businesses that are relevant to your offer&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Get the Ready-Made Workflow
&lt;/h2&gt;

&lt;p&gt;The n8n workflow version (for self-hosted setups) is available at &lt;a href="https://n8nmarkets.com" rel="noopener noreferrer"&gt;n8nmarkets.com&lt;/a&gt;. Search "Google Maps Local Service Leads".&lt;/p&gt;

&lt;p&gt;The Apify actor (no infrastructure needed, pay-per-result) is at &lt;a href="https://apify.com/agoradelmediterraneo/google-maps-business-enricher" rel="noopener noreferrer"&gt;apify.com/agoradelmediterraneo/google-maps-business-enricher&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What niche are you targeting for outreach? Curious to hear what verticals people are using this type of data for.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>automation</category>
      <category>apify</category>
      <category>leadgeneration</category>
    </item>
    <item>
      <title>Never Miss a Stripe Payment Again — Real-Time Alerts on Slack, Discord and Telegram with n8n</title>
      <dc:creator>AgoraIntelligence</dc:creator>
      <pubDate>Sun, 26 Jul 2026 16:21:33 +0000</pubDate>
      <link>https://dev.to/agoraintelligence/never-miss-a-stripe-payment-again-real-time-alerts-on-slack-discord-and-telegram-with-n8n-2ddh</link>
      <guid>https://dev.to/agoraintelligence/never-miss-a-stripe-payment-again-real-time-alerts-on-slack-discord-and-telegram-with-n8n-2ddh</guid>
      <description>&lt;p&gt;Never Miss a Stripe Payment Again — Real-Time Alerts on Slack, Discord and Telegram with n8n&lt;/p&gt;

&lt;p&gt;Stripe processes the payment. You find out 3 hours later when you check your dashboard.&lt;/p&gt;

&lt;p&gt;For solo founders and small teams, missing real-time payment events isn't just inconvenient — it means slower fulfillment, delayed onboarding, and missed opportunities to catch failed payments before customers churn.&lt;/p&gt;

&lt;p&gt;This tutorial shows how to build a Stripe → multi-channel alert system with n8n that notifies your whole team instantly, in whichever app you actually use.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Gets Alerted
&lt;/h2&gt;

&lt;p&gt;The workflow listens to five Stripe webhook events:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;th&gt;Who cares&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;payment_intent.succeeded&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Customer paid&lt;/td&gt;
&lt;td&gt;Everyone 🎉&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;payment_intent.payment_failed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Card declined&lt;/td&gt;
&lt;td&gt;Support team&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;charge.refunded&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Refund issued&lt;/td&gt;
&lt;td&gt;Finance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;customer.subscription.deleted&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Subscription cancelled&lt;/td&gt;
&lt;td&gt;Sales/CS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;invoice.payment_failed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Recurring payment failed&lt;/td&gt;
&lt;td&gt;Finance&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each event generates a formatted message delivered simultaneously to Slack, Discord, Telegram, and/or email — your choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Slack Message Format
&lt;/h2&gt;

&lt;p&gt;Instead of raw JSON, the alerts are formatted for humans:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;💰 New Payment
Amount: €149.00
Customer: Acme Corp (billing@acme.com)
Product: Professional Plan
Payment ID: pi_3abc...
Time: 26 Jul 2026, 14:32
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For failed payments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;⚠️ Payment Failed
Amount: €149.00
Customer: john@example.com
Reason: insufficient_funds
Retry: Stripe will retry in 3 days
Payment ID: pi_3xyz...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  n8n Setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Stripe Webhook
&lt;/h3&gt;

&lt;p&gt;In your Stripe Dashboard → Developers → Webhooks → Add endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Endpoint URL: https://your-n8n.com/webhook/stripe-alerts
Events to send: Select the 5 events listed above
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the &lt;strong&gt;Signing Secret&lt;/strong&gt; — you'll need it to verify webhook authenticity.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Webhook Verification (Security)
&lt;/h3&gt;

&lt;p&gt;Always verify Stripe webhooks. n8n's built-in Stripe Trigger node handles this automatically. If you're using a plain Webhook node, add this verification in a Code node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stripe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// not available in n8n — use the Stripe Trigger node instead&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Use the Stripe Trigger node&lt;/strong&gt; — it handles signature verification out of the box. Don't use a plain Webhook node for Stripe.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The Event Router
&lt;/h3&gt;

&lt;p&gt;After the Stripe Trigger, add a Switch node that routes on &lt;code&gt;{{$json.type}}&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;payment_intent.succeeded&lt;/code&gt; → Branch 1&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;payment_intent.payment_failed&lt;/code&gt; → Branch 2
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;charge.refunded&lt;/code&gt; → Branch 3&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;customer.subscription.deleted&lt;/code&gt; → Branch 4&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;invoice.payment_failed&lt;/code&gt; → Branch 5&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each branch formats the message and sends to your channels.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Message Formatting (Code Node)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;emoji&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;details&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payment_intent.succeeded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;emoji&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;💰&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New Payment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;details&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Amount: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\nCustomer: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Guest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\nID: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payment_intent.payment_failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;emoji&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;⚠️&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Payment Failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;last_payment_error&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unknown reason&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;details&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Amount: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\nReason: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\nID: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;charge.refunded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;emoji&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;↩️&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Refund Issued&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount_refunded&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;details&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Amount refunded: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\nCustomer: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Guest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;emoji&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; *&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;*\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;details&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-GB&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;_`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Multi-Channel Delivery
&lt;/h3&gt;

&lt;p&gt;Connect the formatted message to all your channels in parallel (use &lt;strong&gt;Merge&lt;/strong&gt; node if you want a single execution path):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Slack&lt;/strong&gt; — HTTP Request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;POST&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;https://hooks.slack.com/services/YOUR/WEBHOOK/URL&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Body:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{$json.message}}"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Discord&lt;/strong&gt; — HTTP Request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;POST&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;https://discord.com/api/webhooks/YOUR_WEBHOOK_URL&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Body:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{$json.message}}"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Telegram&lt;/strong&gt; — HTTP Request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;POST&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;https://api.telegram.org/botYOUR_TOKEN/sendMessage&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Body:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"chat_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YOUR_CHAT_ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{$json.message}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"parse_mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Markdown"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Google Sheets Logging
&lt;/h3&gt;

&lt;p&gt;Every event also appends a row to your Sheets log:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csvs"&gt;&lt;code&gt;&lt;span class="k"&gt;Timestamp&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Event&lt;/span&gt; &lt;span class="k"&gt;Type&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Amount&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Currency&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Customer&lt;/span&gt; &lt;span class="k"&gt;Email&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Payment&lt;/span&gt; &lt;span class="k"&gt;ID&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Status&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you a complete audit trail outside Stripe — useful for reconciliation, and for team members who don't have Stripe access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Filtering by Amount (Optional)
&lt;/h2&gt;

&lt;p&gt;Don't want a Slack notification for every $1 microtransaction? Add an IF node before the Slack branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Only alert Slack for payments over €50&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;$json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Stripe amounts in cents&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Discord can still log everything, Slack only gets the significant ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Estimated Setup Time
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Stripe webhook: 5 minutes&lt;/li&gt;
&lt;li&gt;n8n workflow import and configure: 10 minutes&lt;/li&gt;
&lt;li&gt;Slack/Discord/Telegram webhook URLs: 5 minutes each&lt;/li&gt;
&lt;li&gt;Test with Stripe's webhook simulator: 5 minutes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Total: ~30 minutes to first real-time alert.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Get the Pre-Built Workflow
&lt;/h2&gt;

&lt;p&gt;The complete workflow with all 5 event types, formatting logic, and multi-channel delivery configured is available at &lt;a href="https://n8nmarkets.com" rel="noopener noreferrer"&gt;n8nmarkets.com&lt;/a&gt;. Search "Stripe Payment Alerts Slack Discord SMS Email Sheets".&lt;/p&gt;

&lt;p&gt;Includes the workflow JSON ready to import, plus setup guide for each notification channel.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Which Stripe events do you find most useful to monitor in real-time? For most SaaS teams it's failed payments — catching those within minutes instead of hours dramatically improves recovery rates.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>stripe</category>
      <category>automation</category>
      <category>slack</category>
    </item>
    <item>
      <title>Stop Chasing Unpaid Invoices Manually — Automate It with n8n</title>
      <dc:creator>AgoraIntelligence</dc:creator>
      <pubDate>Sun, 26 Jul 2026 16:20:54 +0000</pubDate>
      <link>https://dev.to/agoraintelligence/stop-chasing-unpaid-invoices-manually-automate-it-with-n8n-1khf</link>
      <guid>https://dev.to/agoraintelligence/stop-chasing-unpaid-invoices-manually-automate-it-with-n8n-1khf</guid>
      <description>&lt;p&gt;Stop Chasing Unpaid Invoices Manually — Automate It with n8n&lt;/p&gt;

&lt;p&gt;If you're a freelancer or small business owner, you know the drill: invoice sent, due date passes, silence. You draft a polite reminder, send it, wait. Draft a firmer one. Hate yourself a little. Repeat.&lt;/p&gt;

&lt;p&gt;This tutorial shows how to build a fully automated invoice follow-up system with n8n that sends escalating reminders on its own — and stops the moment someone pays.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Manual Follow-Up
&lt;/h2&gt;

&lt;p&gt;Manual invoice chasing fails in predictable ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You forget (you're busy running the actual business)&lt;/li&gt;
&lt;li&gt;You remember at 11pm and decide to send it "tomorrow"&lt;/li&gt;
&lt;li&gt;Tomorrow you forget again&lt;/li&gt;
&lt;li&gt;45 days overdue you finally send a strongly-worded email that damages the relationship&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Automation solves all three failure modes. The system doesn't forget, doesn't feel awkward, and stops exactly when it should.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Workflow Works
&lt;/h2&gt;

&lt;p&gt;The system reads a Google Sheets invoice list every morning and applies simple date logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;For each invoice where status = "unpaid":
  - 3 days overdue → send reminder_1 (friendly)
  - 7 days overdue → send reminder_2 (firmer)
  - 14+ days overdue → send reminder_3 (urgent) + Slack alert to you
  - Already sent today → skip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you mark an invoice as &lt;code&gt;paid&lt;/code&gt; in the sheet, the system automatically stops. No code needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Google Sheets Structure
&lt;/h2&gt;

&lt;p&gt;Create a sheet with these exact headers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;client_name&lt;/th&gt;
&lt;th&gt;client_email&lt;/th&gt;
&lt;th&gt;amount&lt;/th&gt;
&lt;th&gt;currency&lt;/th&gt;
&lt;th&gt;invoice_date&lt;/th&gt;
&lt;th&gt;due_date&lt;/th&gt;
&lt;th&gt;status&lt;/th&gt;
&lt;th&gt;last_email_sent&lt;/th&gt;
&lt;th&gt;notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Acme Corp&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:billing@acme.com"&gt;billing@acme.com&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2500&lt;/td&gt;
&lt;td&gt;EUR&lt;/td&gt;
&lt;td&gt;2026-07-01&lt;/td&gt;
&lt;td&gt;2026-07-15&lt;/td&gt;
&lt;td&gt;unpaid&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;status&lt;/code&gt;: &lt;code&gt;unpaid&lt;/code&gt; / &lt;code&gt;paid&lt;/code&gt; / &lt;code&gt;disputed&lt;/code&gt; / &lt;code&gt;skip&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;last_email_sent&lt;/code&gt;: filled automatically by the workflow&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;notes&lt;/code&gt;: optional context for the AI-generated emails&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Three Email Tiers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Tier 1 — Friendly Reminder (3 days overdue)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight email"&gt;&lt;code&gt;&lt;span class="nt"&gt;Subject&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="na"&gt; Invoice {{invoice_number}} — Just a quick reminder&lt;/span&gt;

Hi {{client_name}},

I hope all is well. I wanted to send a friendly reminder that invoice 
{{invoice_number}} for {{amount}} {{currency}} was due on {{due_date}}.

If payment has already been sent, please disregard this message. 
If you have any questions, I'm happy to help.

Best regards,
{{your_name}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tier 2 — Direct Follow-Up (7 days overdue)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight email"&gt;&lt;code&gt;&lt;span class="nt"&gt;Subject&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="na"&gt; Invoice {{invoice_number}} — Payment overdue&lt;/span&gt;

Hi {{client_name}},

I'm following up on invoice {{invoice_number}} for {{amount}} {{currency}}, 
which was due on {{due_date}} and remains outstanding.

Could you confirm when we can expect payment? 
If there's an issue I can help resolve, please let me know.

{{your_name}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tier 3 — Escalation (14+ days overdue)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight email"&gt;&lt;code&gt;&lt;span class="nt"&gt;Subject&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="na"&gt; Urgent: Invoice {{invoice_number}} — 14 days overdue&lt;/span&gt;

Hi {{client_name}},

Invoice {{invoice_number}} for {{amount}} {{currency}} is now 14 days overdue.
This is my final reminder before I escalate this matter further.

Please arrange payment by {{date_5_days_from_now}} or contact me to discuss.

{{your_name}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At Tier 3, the workflow also sends a Slack message to alert you that this invoice needs your personal attention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key n8n Nodes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Schedule Trigger&lt;/strong&gt; — runs every day at 8am&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Google Sheets Read&lt;/strong&gt; — reads all rows where &lt;code&gt;status = unpaid&lt;/code&gt;&lt;br&gt;&lt;br&gt;
Uses HTTP Request to Sheets API v4 (not the native node — it's unreliable for filtered reads):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET https://sheets.googleapis.com/v4/spreadsheets/{{SHEET_ID}}/values/Sheet1!A:I
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Code Node — Calculate Days Overdue&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dueDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;due_date&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;daysOverdue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;today&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;dueDate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lastSent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;last_email_sent&lt;/span&gt; 
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;last_email_sent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sentToday&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lastSent&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;lastSent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toDateString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toDateString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;daysOverdue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;sentToday&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;daysOverdue&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;daysOverdue&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;daysOverdue&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. IF Node&lt;/strong&gt; — filters to &lt;code&gt;tier &amp;gt; 0 AND NOT sentToday&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Switch Node&lt;/strong&gt; — routes to the correct email template based on tier&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Gmail/SMTP Send&lt;/strong&gt; — sends the email&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Google Sheets Update&lt;/strong&gt; — marks &lt;code&gt;last_email_sent&lt;/code&gt; with today's date&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. IF Node (Tier 3 only)&lt;/strong&gt; — sends Slack alert&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stop Logic
&lt;/h2&gt;

&lt;p&gt;This is the most important part: &lt;strong&gt;the system never emails a paid client.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The workflow reads the &lt;code&gt;status&lt;/code&gt; column first. If status is &lt;code&gt;paid&lt;/code&gt;, &lt;code&gt;disputed&lt;/code&gt;, or &lt;code&gt;skip&lt;/code&gt;, the row is filtered out before any date calculation. Change the status in the sheet → emails stop immediately, no workflow restart needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results: What to Expect
&lt;/h2&gt;

&lt;p&gt;From building this for several service businesses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Average days-to-payment drops from 28 to 11&lt;/strong&gt; when automated reminders are active&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fewer awkward conversations&lt;/strong&gt; — clients treat automated reminders differently than personal ones&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero maintenance&lt;/strong&gt; once set up — it just runs every morning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The psychological trick: clients know automated systems don't hold grudges. They pay the automated reminder without the social friction of ignoring a personal message.&lt;/p&gt;

&lt;h2&gt;
  
  
  Costs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;n8n&lt;/strong&gt;: $0 self-hosted&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gmail API&lt;/strong&gt;: Free (standard Gmail limits apply)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Sheets&lt;/strong&gt;: Free&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slack&lt;/strong&gt;: Free for basic webhooks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Total: $0/month if you're already using these tools.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Get the Ready-to-Import Workflow
&lt;/h2&gt;

&lt;p&gt;The complete workflow JSON — with all nodes configured, the Sheets template, and Gmail/SMTP setup guide — is available at &lt;a href="https://n8nmarkets.com" rel="noopener noreferrer"&gt;n8nmarkets.com&lt;/a&gt;. Search "Invoice Chaser Auto Follow-Up".&lt;/p&gt;

&lt;p&gt;Includes: workflow JSON, Google Sheets template with exact headers, 3 email templates in English and Spanish, and a troubleshooting guide for common Gmail OAuth issues.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What's your current average days-to-payment? And has automated follow-up changed that number for you? Curious to see real data in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>automation</category>
      <category>freelance</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Build an AI WhatsApp Customer Service Bot with n8n (No Code, 30 Minutes)</title>
      <dc:creator>AgoraIntelligence</dc:creator>
      <pubDate>Sun, 26 Jul 2026 16:19:17 +0000</pubDate>
      <link>https://dev.to/agoraintelligence/build-an-ai-whatsapp-customer-service-bot-with-n8n-no-code-30-minutes-404d</link>
      <guid>https://dev.to/agoraintelligence/build-an-ai-whatsapp-customer-service-bot-with-n8n-no-code-30-minutes-404d</guid>
      <description>&lt;p&gt;Your WhatsApp Business account receives a message at 11pm. It's a potential client asking about your services. By the time you see it tomorrow morning, they've already bought from a competitor.&lt;/p&gt;

&lt;p&gt;This tutorial shows you how to build a 24/7 AI-powered WhatsApp customer service bot using n8n that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Classifies&lt;/strong&gt; every incoming message (urgent complaint, booking request, general info)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replies automatically&lt;/strong&gt; with AI-generated responses personalized to your business&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escalates&lt;/strong&gt; to your phone via Telegram when something actually needs your attention&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logs everything&lt;/strong&gt; to Google Sheets for review&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No coding required. Setup time: ~30 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;The workflow has 9 nodes and follows a simple decision tree:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WhatsApp webhook → Parse message → Classify intent (GPT-4o-mini)
       ↓
   URGENT? → Yes → Telegram alert + log
       ↓ No
   Generate AI reply → Send via WhatsApp API → Log to Sheets → Respond 200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Intent Classification
&lt;/h3&gt;

&lt;p&gt;The OpenAI node classifies each message into one of four intents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;URGENT&lt;/code&gt; — complaints, cancellation threats, problems ("the product arrived broken")&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;INFO&lt;/code&gt; — general questions ("what are your hours?", "do you deliver to Madrid?")
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BOOKING&lt;/code&gt; — appointment requests ("I'd like to book for Friday at 5pm")&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;OTHER&lt;/code&gt; — anything else&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the key logic that decides whether you get woken up or the bot handles it silently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-Step Setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. WhatsApp Cloud API (Meta)
&lt;/h3&gt;

&lt;p&gt;Go to &lt;a href="https://developers.facebook.com" rel="noopener noreferrer"&gt;developers.facebook.com&lt;/a&gt;, create an app, add WhatsApp product, and get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Phone Number ID&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access Token&lt;/strong&gt; (temporary → convert to permanent)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify Token&lt;/strong&gt; (you invent this, e.g. &lt;code&gt;my_verify_token_2026&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Set up the webhook to point to your n8n instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;Webhook&lt;/span&gt; &lt;span class="n"&gt;URL&lt;/span&gt;: &lt;span class="n"&gt;https&lt;/span&gt;://&lt;span class="n"&gt;your&lt;/span&gt;-&lt;span class="n"&gt;n8n&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;/&lt;span class="n"&gt;webhook&lt;/span&gt;/&lt;span class="n"&gt;whatsapp&lt;/span&gt;-&lt;span class="n"&gt;bot&lt;/span&gt;
&lt;span class="n"&gt;Subscribed&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;: &lt;span class="n"&gt;messages&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. The n8n Webhook Node
&lt;/h3&gt;

&lt;p&gt;Configure as &lt;strong&gt;POST&lt;/strong&gt;, path: &lt;code&gt;whatsapp-bot&lt;/code&gt;. The webhook also needs to handle GET requests for Meta's verification challenge — add a second route or use an IF node to detect the &lt;code&gt;hub.challenge&lt;/code&gt; parameter.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The CONFIG Node (the only node you edit)
&lt;/h3&gt;

&lt;p&gt;All settings live in one Code node at the top:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CONFIG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// WhatsApp&lt;/span&gt;
  &lt;span class="na"&gt;wa_phone_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YOUR_PHONE_NUMBER_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;wa_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YOUR_WHATSAPP_ACCESS_TOKEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// Telegram escalation  &lt;/span&gt;
  &lt;span class="na"&gt;telegram_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YOUR_BOT_TOKEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;telegram_chat_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YOUR_CHAT_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// Google Sheets&lt;/span&gt;
  &lt;span class="na"&gt;sheet_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YOUR_SPREADSHEET_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;gcp_service_account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vcWuMisYV6Kfx8Vv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// your n8n credential ID&lt;/span&gt;

  &lt;span class="c1"&gt;// AI context — this is what makes replies feel personalized&lt;/span&gt;
  &lt;span class="na"&gt;business_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Clínica Dental Sánchez&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;business_description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Clínica dental en Madrid, servicios: limpiezas, ortodoncia, implantes. Horario L-V 9-20h, S 9-14h.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;escalation_keywords&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;urgente&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;problema&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;queja&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cancelar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;devolver&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;openai_credential_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bC16OOi5YkUp5C10&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// Fallback if OpenAI fails&lt;/span&gt;
  &lt;span class="na"&gt;fallback_reply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Gracias por tu mensaje. Te responderemos lo antes posible en horario laboral.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CONFIG&lt;/span&gt; &lt;span class="p"&gt;}];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. The AI Reply Generation
&lt;/h3&gt;

&lt;p&gt;The OpenAI node that generates replies uses this prompt structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are a customer service assistant for {{business_name}}.

Business context: {{business_description}}

Customer message: {{message_text}}
Message intent: {{classified_intent}}

Reply professionally in the same language as the customer's message. 
Keep it under 3 sentences. Don't make up information not in the business context.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what makes the bot sound like it actually knows your business — not a generic chatbot.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Send the WhatsApp Reply
&lt;/h3&gt;

&lt;p&gt;The HTTP Request node sends back via the Cloud API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST https://graph.facebook.com/v19.0/{{wa_phone_id}}/messages
Authorization: Bearer {{wa_token}}
Content-Type: application/json

{
  "messaging_product": "whatsapp",
  "to": "{{sender_phone}}",
  "type": "text",
  "text": { "body": "{{ai_reply}}" }
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. The Critical Last Node: Respond 200
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;This is the most important node that most tutorials miss.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;WhatsApp requires your webhook to respond with HTTP 200 within 20 seconds. If it doesn't, Meta marks your webhook as unhealthy and stops sending events.&lt;/p&gt;

&lt;p&gt;Add a &lt;strong&gt;Respond to Webhook&lt;/strong&gt; node at the very end of every branch (urgent AND non-urgent) that returns &lt;code&gt;{"status": "ok"}&lt;/code&gt; with status 200.&lt;/p&gt;

&lt;h2&gt;
  
  
  Costs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;n8n&lt;/strong&gt;: $0 if self-hosted, $20/month on n8n Cloud&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WhatsApp Cloud API&lt;/strong&gt;: Free for the first 1,000 conversations/month (Meta's pricing)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI GPT-4o-mini&lt;/strong&gt;: ~$0.00015 per message. At 100 messages/day: ~$0.46/month&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Telegram bot&lt;/strong&gt;: Free&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Total for a small business with 100 messages/day: ~$0.46/month in AI costs.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Handles (and What It Doesn't)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Handles well:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repeated FAQ questions (hours, pricing, location, delivery)&lt;/li&gt;
&lt;li&gt;Booking confirmations&lt;/li&gt;
&lt;li&gt;First contact from new customers&lt;/li&gt;
&lt;li&gt;After-hours messages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Needs human review:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complaints about specific orders (bot can acknowledge, but you close the ticket)&lt;/li&gt;
&lt;li&gt;Anything requiring account access or personal data lookup&lt;/li&gt;
&lt;li&gt;Complex negotiations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Telegram escalation handles the "needs human review" bucket automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo: What a Conversation Looks Like
&lt;/h2&gt;

&lt;p&gt;Customer sends: &lt;em&gt;"Hola, ¿tenéis cita disponible para limpieza este sábado por la mañana?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Bot replies (in ~3 seconds): &lt;em&gt;"¡Hola! Sí, tenemos citas disponibles los sábados de 9 a 14h. Para reservar, díganos su nombre y el horario preferido y lo gestionamos enseguida."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Customer sends: &lt;em&gt;"El producto que compré llegó roto, exijo una solución AHORA"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Bot triggers Telegram alert to you: 🚨 &lt;strong&gt;URGENTE&lt;/strong&gt; | +34612345678 | "El producto que compré llegó roto, exijo una solución AHORA"&lt;/p&gt;

&lt;p&gt;You reply personally. The customer feels heard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get the Ready-Made Workflow
&lt;/h2&gt;

&lt;p&gt;If you want to skip the manual setup, the complete workflow with all nodes pre-configured is available on &lt;a href="https://n8nmarkets.com" rel="noopener noreferrer"&gt;n8nmarkets.com&lt;/a&gt; — search "WhatsApp Telegram AI Customer Service Agent".&lt;/p&gt;

&lt;p&gt;It includes the workflow JSON, README with screenshots, and curl commands to test every node before going live.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built something similar? Share your setup in the comments — especially if you've handled the webhook verification challenge in a clever way.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>whatsapp</category>
      <category>automation</category>
      <category>ai</category>
    </item>
    <item>
      <title>Querying EU Company Registries from Python — No Dun &amp; Bradstreet Required</title>
      <dc:creator>AgoraIntelligence</dc:creator>
      <pubDate>Tue, 21 Jul 2026 10:01:02 +0000</pubDate>
      <link>https://dev.to/agoraintelligence/querying-eu-company-registries-from-python-no-dun-bradstreet-required-62l</link>
      <guid>https://dev.to/agoraintelligence/querying-eu-company-registries-from-python-no-dun-bradstreet-required-62l</guid>
      <description>&lt;h1&gt;
  
  
  Querying EU Company Registries from Python — No Dun &amp;amp; Bradstreet Required
&lt;/h1&gt;

&lt;p&gt;I was doing due diligence on a Spanish startup last month. Needed to verify they were registered with the tax authority, check their filing status, and see if there were any red flags in their recent updates. The usual approach would be to pay Dun &amp;amp; Bradstreet or similar. Instead, I built a Python wrapper around BORME (Spain's official business registry) and figured if it saved me time, it might save someone else time too.&lt;/p&gt;

&lt;p&gt;The problem: BORME exists, it's free, it's official government data — 2.8M Spanish companies. But accessing it requires navigating redirects, session handling, and understanding their archaic query format. Same story with SIRENE in France or Companies House in the UK. Each one is public, but each one is a different puzzle.&lt;/p&gt;

&lt;h2&gt;
  
  
  The quick version
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="c1"&gt;# Query the BORME registry for a Spanish company
&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.rapidapi.com/v1/spain/borme/company&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Telefonica&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-RapidAPI-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;companies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;company&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;companies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; — VAT: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tax_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; — Active: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;active&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Three lines to pull official data from Spain's registry, no scraping, no session cookies to manage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built this
&lt;/h2&gt;

&lt;p&gt;When I need to check a European company, I want:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Official data only&lt;/strong&gt; — not aggregators, not guesses. The Spanish government maintains BORME, I want BORME.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unified interface&lt;/strong&gt; — BORME, SIRENE, Companies House all use different query models. I want one Python pattern that works for all three.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No licensing costs&lt;/strong&gt; — The data is public. Why should I pay $50/month to access something governments publish for free?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The workflow I ended up with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Query BORME for Spanish companies (2.8M records)&lt;/li&gt;
&lt;li&gt;Query SIRENE for French companies (25M records) — same pattern, different country&lt;/li&gt;
&lt;li&gt;Query Companies House for UK companies (5M records)&lt;/li&gt;
&lt;li&gt;Cross-reference the results, flag any discrepancies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the startup I was checking, it took 30 seconds to confirm they were registered since 2019, had filed their 2024 statements, and had no liquidation notices. That's due diligence done.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's available now
&lt;/h2&gt;

&lt;p&gt;I packaged this into a set of APIs so I don't have to rebuild it each time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Spain BORME&lt;/strong&gt;: 2.8M companies, updated daily&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;France SIRENE&lt;/strong&gt;: 25M companies, official INSEE data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UK Companies House&lt;/strong&gt;: 5M companies, Companies House direct feed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EU Public Tenders TED&lt;/strong&gt;: 300K+ procurement notices, searchable by keyword and date&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spain AEMPS&lt;/strong&gt;: 50K medicines and their approval status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All exposed as REST endpoints. No auth required for the free tier, just pass your API key. Query any of them from Python, Node, Go, whatever — it's just HTTP.&lt;/p&gt;

&lt;h2&gt;
  
  
  The limitation you should know about
&lt;/h2&gt;

&lt;p&gt;This works great for structured queries: "Give me all companies matching X in country Y." It doesn't do fuzzy matching on company names — garbage in, garbage out. If you search for "Telefonica" it'll find exact matches or close variants in the registry, but if you search for "tele fonica" with a space, it'll miss them. You need to clean your input.&lt;/p&gt;

&lt;p&gt;Also, BORME updates daily but with a lag — if a company filed something this morning, it might not show for 24 hours. SIRENE can be slower. Know your latency tolerance before you build something that expects real-time data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;If you're doing KYC, compliance, or just need to verify a European company without a subscription service, grab a key and give it a try. Happy to answer questions about the data sources or the setup in the comments.&lt;/p&gt;

&lt;p&gt;Happy to answer questions about the data sources or the setup.&lt;/p&gt;

</description>
      <category>api</category>
      <category>python</category>
      <category>europe</category>
      <category>automation</category>
    </item>
    <item>
      <title>Test pub</title>
      <dc:creator>AgoraIntelligence</dc:creator>
      <pubDate>Tue, 21 Jul 2026 09:23:16 +0000</pubDate>
      <link>https://dev.to/agoraintelligence/test-pub-3f0g</link>
      <guid>https://dev.to/agoraintelligence/test-pub-3f0g</guid>
      <description>&lt;p&gt;test body&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Draft test 2</title>
      <dc:creator>AgoraIntelligence</dc:creator>
      <pubDate>Tue, 21 Jul 2026 09:22:58 +0000</pubDate>
      <link>https://dev.to/agoraintelligence/draft-test-2-5061</link>
      <guid>https://dev.to/agoraintelligence/draft-test-2-5061</guid>
      <description>&lt;p&gt;test body&lt;/p&gt;

</description>
    </item>
    <item>
      <title>I built MCP servers so Claude can query official EU company registries directly</title>
      <dc:creator>AgoraIntelligence</dc:creator>
      <pubDate>Sun, 12 Jul 2026 11:45:17 +0000</pubDate>
      <link>https://dev.to/agoraintelligence/i-built-mcp-servers-so-claude-can-query-official-eu-company-registries-directly-53a1</link>
      <guid>https://dev.to/agoraintelligence/i-built-mcp-servers-so-claude-can-query-official-eu-company-registries-directly-53a1</guid>
      <description>&lt;p&gt;I was doing compliance work for a client — verifying that a list of Spanish suppliers were actually active companies with no recent insolvencies. The standard approach is to go to BORME (Spain's official business registry), search each company manually, and copy the relevant fields into a spreadsheet. For 40 companies that's an afternoon.&lt;/p&gt;

&lt;p&gt;It occurred to me that Claude could do most of this if it had access to the actual registry data. So I built an MCP server for it.&lt;/p&gt;

&lt;p&gt;The BORME API returns real-time data — company status, directors, capital, corporate acts history. Once it's connected via MCP, you can just ask: "Check these 40 company names against the Spanish registry and flag any that are dissolved or in insolvency proceedings." Claude handles the lookup and summarization.&lt;/p&gt;

&lt;p&gt;I ended up building 7 servers total:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spain BORME (2.8M companies, updated daily from the official BOE feed)&lt;/li&gt;
&lt;li&gt;France SIRENE (25M companies via api.gouv.fr)&lt;/li&gt;
&lt;li&gt;UK Companies House (official CH API)&lt;/li&gt;
&lt;li&gt;Spain BDNS (500K+ government grants and subsidies)&lt;/li&gt;
&lt;li&gt;Spain AEMPS (pharmaceutical registry — useful for verifying authorized medicines)&lt;/li&gt;
&lt;li&gt;LATAM (Brazil CNPJ lookups, tax ID validation for MX/AR/PE)&lt;/li&gt;
&lt;li&gt;SEC EDGAR (US public company filings)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of them use the official government APIs directly, so the data is as fresh as the source. No intermediary databases that might be weeks behind.&lt;/p&gt;

&lt;p&gt;They're live on Smithery under the &lt;code&gt;fzth-ia-it&lt;/code&gt; namespace if you want to try them. Free tier covers most use cases — higher volume plans available if you're running it in production.&lt;/p&gt;

&lt;p&gt;A few things I learned building these:&lt;/p&gt;

&lt;p&gt;Government APIs are surprisingly inconsistent. The French SIRENE API is excellent — well-documented, reliable, good rate limits. BORME required more work to parse because the official format is designed for lawyers, not developers. Companies House is solid but goes down for maintenance on Tuesday mornings UK time.&lt;/p&gt;

&lt;p&gt;The MCP transport layer (Streamable HTTP) was simpler to implement than I expected. The main complexity was handling pagination and rate limits gracefully so Claude doesn't get stuck waiting on slow responses.&lt;/p&gt;

&lt;p&gt;Happy to answer questions about any of the specific data sources or the MCP implementation.&lt;/p&gt;

</description>
      <category>claude</category>
      <category>mcp</category>
      <category>api</category>
      <category>europe</category>
    </item>
  </channel>
</rss>
