<?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: Stepan Nikonov</title>
    <description>The latest articles on DEV Community by Stepan Nikonov (@floxolab).</description>
    <link>https://dev.to/floxolab</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%2F3997568%2Fdc0559e7-3a3a-4f2c-9dfe-01c4402a72ff.jpg</url>
      <title>DEV Community: Stepan Nikonov</title>
      <link>https://dev.to/floxolab</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/floxolab"/>
    <language>en</language>
    <item>
      <title>How to Deduplicate Leads in n8n Before They Reach Your CRM</title>
      <dc:creator>Stepan Nikonov</dc:creator>
      <pubDate>Tue, 18 Aug 2026 16:07:37 +0000</pubDate>
      <link>https://dev.to/floxolab/how-to-deduplicate-leads-in-n8n-before-they-reach-your-crm-3jd0</link>
      <guid>https://dev.to/floxolab/how-to-deduplicate-leads-in-n8n-before-they-reach-your-crm-3jd0</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Normalize each lead, compare the right identity keys, update an existing contact when the match is safe, and send ambiguous records to review instead of polluting the CRM.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Lead deduplication is not one filter. A reliable n8n workflow separates repeated submissions from repeated people, normalizes the values used for comparison, checks persistent records, and lets the CRM enforce uniqueness whenever the destination supports it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Short answer:&lt;/strong&gt; use a stable source ID to stop the same event twice, normalized email or phone to find an existing person, and a CRM-native create/update or upsert operation for the final write. Never auto-merge two records on name alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  First identify which duplicate you are stopping
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Duplicate type&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Best first control&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repeated item in one execution&lt;/td&gt;
&lt;td&gt;A spreadsheet import contains the same row twice&lt;/td&gt;
&lt;td&gt;Remove Duplicates on the current input&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repeated source event&lt;/td&gt;
&lt;td&gt;A webhook or retry delivers submission &lt;code&gt;lead_8472&lt;/code&gt; again&lt;/td&gt;
&lt;td&gt;Persistent check on the stable source ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same person from another source&lt;/td&gt;
&lt;td&gt;The same customer submits a website form after a Facebook form&lt;/td&gt;
&lt;td&gt;Normalized identity lookup in the CRM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conflicting identity&lt;/td&gt;
&lt;td&gt;Email matches one contact while phone matches another&lt;/td&gt;
&lt;td&gt;Human review, not an automatic merge&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;a href="https://floxolab.com/blog/facebook-lead-ads-google-sheets-email-without-zapier" rel="noopener noreferrer"&gt;Facebook Lead Ads workflow&lt;/a&gt; uses a Meta lead ID to stop one submission from creating several sheet rows. This guide goes further by handling several sources and an existing CRM full of contacts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose identity keys before building nodes
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;What it proves&lt;/th&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Source record or submission ID&lt;/td&gt;
&lt;td&gt;The same source event or object&lt;/td&gt;
&lt;td&gt;Strongest key for replay protection, but not for matching a person across systems&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Normalized email&lt;/td&gt;
&lt;td&gt;Usually the same inbox&lt;/td&gt;
&lt;td&gt;Good exact contact key when present and valid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Normalized phone&lt;/td&gt;
&lt;td&gt;Usually the same reachable number&lt;/td&gt;
&lt;td&gt;Useful with country context; shared business or family numbers need caution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Name&lt;/td&gt;
&lt;td&gt;Only that the text looks similar&lt;/td&gt;
&lt;td&gt;Use as review context, never as the only automatic merge key&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Decide field ownership too. A new form may fill a missing phone number, but it should not silently replace a verified CRM email, owner, lifecycle stage, or consent value. Deduplication decides which record to use. A separate update policy decides which fields may change.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical n8n workflow shape
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Lead trigger
  → Edit Fields: normalize keys
  → Remove Duplicates: current batch or repeated event
  → Data Table or CRM: persistent lookup
  → Switch: new / exact match / conflict
  → CRM: create or update
  → Data Table: record source key and CRM ID
  → Alert only when review or failure is needed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start with one lead source and one CRM. The same pattern can later sit behind Facebook forms, website forms, imports, chat, and email without giving each source a different duplicate policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: normalize the fields once
&lt;/h2&gt;

&lt;p&gt;Add an &lt;strong&gt;Edit Fields&lt;/strong&gt; node immediately after the trigger. Preserve the original values for audit and display, then create separate comparison fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source_key
email_original
email_key
phone_original
phone_key
full_name
source
received_at
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For an email comparison key, an n8n expression can trim whitespace and use lowercase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{ ($json.email || '').trim().toLowerCase() }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build &lt;code&gt;source_key&lt;/code&gt; from the provider and its stable ID, for example &lt;code&gt;facebook:lead_8472&lt;/code&gt; or &lt;code&gt;website:submission_193&lt;/code&gt;. Do not concatenate name and timestamp and call that stable.&lt;/p&gt;

&lt;p&gt;For phone numbers, remove presentation punctuation and normalize with the correct country context. Simply stripping every non-digit character can make two different international numbers look deceptively similar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: use Remove Duplicates for the narrow job it does well
&lt;/h2&gt;

&lt;p&gt;For duplicate rows arriving together, configure &lt;strong&gt;Remove Duplicates → Remove Items Repeated Within Current Input&lt;/strong&gt;, choose &lt;strong&gt;Selected Fields&lt;/strong&gt;, and compare &lt;code&gt;source_key&lt;/code&gt;. This is ideal for imports and batched records.&lt;/p&gt;

&lt;p&gt;For the same source event arriving in later executions, the node also supports &lt;strong&gt;Remove Items Processed in Previous Executions&lt;/strong&gt;. Use &lt;strong&gt;Value Is New&lt;/strong&gt; and deduplicate on &lt;code&gt;source_key&lt;/code&gt;. Scope can be the individual node or the workflow. Current n8n documentation says the default history size is 10,000 items.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Do not treat that history as your customer database.&lt;/strong&gt; The history has a configured size, can be cleared, and only knows the values you gave that node or workflow. It cannot decide that two different source IDs belong to the same person.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 3: add a persistent lookup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Option A: n8n Data Table for a small lead ledger
&lt;/h3&gt;

&lt;p&gt;A Data Table is a practical first version for a small business that does not yet have strong CRM lookup rules. Create a table such as &lt;code&gt;lead_identity&lt;/code&gt; with these columns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source_key
email_key
phone_key
crm_id
status
first_seen_at
last_seen_at
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The current Data Table node can check whether a row exists, get rows, insert, update, and upsert using conditions. Search the strongest available key before the CRM write, then upsert the final &lt;code&gt;crm_id&lt;/code&gt; only after the CRM succeeds.&lt;/p&gt;

&lt;p&gt;n8n describes Data Tables as light to moderate storage. The default total limit across all tables in an instance is 50 MB, although self-hosted instances can change it. That makes a table useful as a compact identity ledger, not a replacement for a mature CRM or warehouse.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option B: search the CRM directly
&lt;/h3&gt;

&lt;p&gt;If the CRM already owns the customer record, query it first. The current n8n HubSpot node, for example, supports searching contacts and a &lt;strong&gt;Create/Update a contact&lt;/strong&gt; operation. Other CRM nodes expose different operations, so check the destination's exact contract before copying a pattern.&lt;/p&gt;

&lt;p&gt;Prefer a CRM record ID or custom unique source ID once you have one. HubSpot's current Contacts API can retrieve by email and batch upsert by email or a custom unique identifier. It also notes that partial upserts are not supported when email is the identifier, which is one reason not to send an incomplete payload blindly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: route new, matched, and conflicting leads
&lt;/h2&gt;

&lt;p&gt;Use a &lt;strong&gt;Switch&lt;/strong&gt; node with three explicit outcomes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No match:&lt;/strong&gt; create the contact, then save the returned CRM ID with the source key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One safe match:&lt;/strong&gt; update only allowed fields and attach the new source or submission as an activity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conflict:&lt;/strong&gt; do not create or merge automatically. Send the candidate record IDs and incoming values to an owner for review.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A conflict includes more than two search results. It also includes email matching contact A while phone matches contact B, or a source ID pointing to a record whose verified identity now disagrees with the payload. Preserve the lead and notify someone. Silent deletion is not deduplication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: make the CRM write duplicate-safe
&lt;/h2&gt;

&lt;p&gt;Use the destination's create/update or upsert operation when it can enforce a unique identifier. This is stronger than a separate search followed by create because two n8n executions can run at nearly the same time, both see no result, and both create a contact before either writes the ledger.&lt;/p&gt;

&lt;p&gt;If the CRM cannot upsert atomically, use its unique-field constraint, serialize the critical section, or put the identity key in a database with a unique index. Catch the destination's duplicate response and retrieve the existing record instead of treating every conflict as an unrecoverable failure.&lt;/p&gt;

&lt;p&gt;After a successful write, upsert the source key and returned CRM ID into the Data Table. Then send notifications or start follow-up. This ordering prevents the team from receiving a success alert for a contact that never reached the CRM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test with a small duplicate matrix
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;Expected result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Same source ID delivered twice&lt;/td&gt;
&lt;td&gt;One CRM record; the replay is stopped or attached without another create&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Different source ID, same normalized email&lt;/td&gt;
&lt;td&gt;Existing contact updated according to field rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same phone with different formatting&lt;/td&gt;
&lt;td&gt;One candidate after country-aware normalization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same name, different email and phone&lt;/td&gt;
&lt;td&gt;New record or manual review, never an automatic merge by name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Email matches A, phone matches B&lt;/td&gt;
&lt;td&gt;Conflict branch with both record IDs visible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two identical executions started together&lt;/td&gt;
&lt;td&gt;One destination record because the final write enforces uniqueness&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CRM write fails&lt;/td&gt;
&lt;td&gt;No success alert and no ledger row marked complete&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Comparing raw email or phone values without normalization.&lt;/li&gt;
&lt;li&gt;Using name as a unique key.&lt;/li&gt;
&lt;li&gt;Assuming a source submission ID identifies the same person across every channel.&lt;/li&gt;
&lt;li&gt;Dropping duplicates without recording the new campaign, form, message, or timestamp.&lt;/li&gt;
&lt;li&gt;Overwriting verified CRM fields with blank or lower-confidence form values.&lt;/li&gt;
&lt;li&gt;Running search and create concurrently without a destination uniqueness control.&lt;/li&gt;
&lt;li&gt;Using a temporary in-workflow list as the only long-term duplicate barrier.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Useful questions before publishing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can Remove Duplicates replace a CRM lookup?
&lt;/h3&gt;

&lt;p&gt;No. It is useful for repeated items and remembered values, but it does not understand an existing customer record, alternate email, shared phone, merge history, or field ownership.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should email or phone be the main key?
&lt;/h3&gt;

&lt;p&gt;Use the strongest verified identifier your process reliably collects. Email is often the simplest contact key. Phone can be useful after country-aware normalization. Keep a stable source ID for replay protection even when email is the CRM match key.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should a duplicate lead be discarded?
&lt;/h3&gt;

&lt;p&gt;Usually not. The person may have submitted a new service request or responded to a different campaign. Reuse the contact record, then preserve the new submission as an activity, note, deal, or source event.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy and operating rules
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Store only the identity fields needed for matching and follow-up.&lt;/li&gt;
&lt;li&gt;Restrict Data Tables, execution data, and CRM credentials to the responsible project members.&lt;/li&gt;
&lt;li&gt;Set retention rules for rejected and ambiguous records.&lt;/li&gt;
&lt;li&gt;Do not place full lead payloads in chat alerts when a secure CRM link is enough.&lt;/li&gt;
&lt;li&gt;Document who resolves conflicts and how quickly the review queue should be checked.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For retries around state-changing API calls, use the separate &lt;a href="https://floxolab.com/blog/n8n-retry-failed-api-requests-without-duplicates" rel="noopener noreferrer"&gt;duplicate-safe n8n API retry pattern&lt;/a&gt;. That guide protects one operation from being repeated. This guide protects CRM identity across leads and sources.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources checked
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.removeduplicates/" rel="noopener noreferrer"&gt;n8n Remove Duplicates operations, scope, history, and default history size&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.n8n.io/data/data-tables/" rel="noopener noreferrer"&gt;n8n Data Tables uses, access, and storage limitations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.datatable/rows/" rel="noopener noreferrer"&gt;n8n Data Table row checks and upsert operation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.hubspot/" rel="noopener noreferrer"&gt;n8n HubSpot contact search and create/update operations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.hubspot.com/docs/api-reference/latest/crm/objects/contacts/guide" rel="noopener noreferrer"&gt;HubSpot contact identifiers and batch upsert behavior&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://floxolab.com/blog/n8n-deduplicate-leads-before-crm" rel="noopener noreferrer"&gt;FloxoLab&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>automation</category>
      <category>crm</category>
      <category>data</category>
    </item>
    <item>
      <title>How to Back Up and Restore Self-Hosted n8n</title>
      <dc:creator>Stepan Nikonov</dc:creator>
      <pubDate>Sat, 15 Aug 2026 21:21:07 +0000</pubDate>
      <link>https://dev.to/floxolab/how-to-back-up-and-restore-self-hosted-n8n-1co0</link>
      <guid>https://dev.to/floxolab/how-to-back-up-and-restore-self-hosted-n8n-1co0</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A workflow export is not a full instance backup. Protect the database, encryption key, deployment configuration, and binary storage, then prove that they work together on an isolated restore.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A recoverable self-hosted n8n backup has four core parts: the database, the original encryption key, the deployment configuration and secrets, and any binary-data storage your executions still need.&lt;/p&gt;

&lt;p&gt;Back up the persistent &lt;code&gt;.n8n&lt;/code&gt; volume even when PostgreSQL stores the main database. n8n documents that this directory can still contain the encryption key, logs, and source-control assets.&lt;/p&gt;

&lt;h2&gt;
  
  
  What must be in the backup
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Asset&lt;/th&gt;
&lt;th&gt;Why it matters&lt;/th&gt;
&lt;th&gt;Typical location&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;Workflows, encrypted credentials, users, projects, settings, and retained execution data&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;~/.n8n/database.sqlite&lt;/code&gt; or PostgreSQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encryption key&lt;/td&gt;
&lt;td&gt;Decrypts credentials stored in the database&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;~/.n8n&lt;/code&gt; settings or &lt;code&gt;N8N_ENCRYPTION_KEY&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment config&lt;/td&gt;
&lt;td&gt;Restores database connection, public URL, proxy, timezone, pruning, and execution behavior&lt;/td&gt;
&lt;td&gt;Compose file, environment settings, secret references, proxy config&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Binary data&lt;/td&gt;
&lt;td&gt;Restores retained files handled by executions&lt;/td&gt;
&lt;td&gt;Persistent filesystem, database, or configured external store&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom and community nodes&lt;/td&gt;
&lt;td&gt;Allows restored workflows to load the same node types and versions&lt;/td&gt;
&lt;td&gt;Package list, custom-node directory, container image, or build manifest&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Do not put the raw encryption key, database password, OAuth secrets, or an unredacted environment file in a public repository. Keep a sanitized deployment definition in version control and protect the actual secrets in a restricted password manager, secret store, or encrypted backup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose the database path first
&lt;/h2&gt;

&lt;p&gt;Self-hosted n8n uses SQLite by default. The database file is &lt;code&gt;~/.n8n/database.sqlite&lt;/code&gt;. PostgreSQL is the other supported database option. The backup and restore commands must match the database your instance actually uses.&lt;/p&gt;

&lt;h3&gt;
  
  
  SQLite: capture a consistent persistent volume
&lt;/h3&gt;

&lt;p&gt;The simplest small-instance method is a short maintenance window: stop the n8n application, archive its persistent volume, then start it again. Stopping writes avoids treating an arbitrary live file copy as a consistent database backup.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose stop n8n
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; n8n_data:/data:ro &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;/backups:/backup"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  alpine &lt;span class="nb"&gt;tar &lt;/span&gt;czf /backup/n8n-data-2026-07-18.tgz &lt;span class="nt"&gt;-C&lt;/span&gt; /data &lt;span class="nb"&gt;.&lt;/span&gt;
docker compose start n8n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the service and volume names with the names in your deployment. If downtime is not acceptable, use a storage snapshot or SQLite-aware backup process that guarantees a consistent result. Do not assume that copying &lt;code&gt;database.sqlite&lt;/code&gt; while executions are writing to it is safe.&lt;/p&gt;

&lt;h3&gt;
  
  
  PostgreSQL: use a database dump
&lt;/h3&gt;

&lt;p&gt;PostgreSQL's official documentation recommends &lt;code&gt;pg_dump&lt;/code&gt; for logical backups. A custom-format dump works with &lt;code&gt;pg_restore&lt;/code&gt; and is practical for restoring into a fresh database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pg_dump &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;custom &lt;span class="nt"&gt;--file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;n8n-2026-07-18.dump n8n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dump does not replace the n8n persistent volume or explicit encryption key. It also does not capture the Compose file, environment settings, proxy configuration, or filesystem binary data. Store those as separate parts of the same dated backup set.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add native n8n exports as a second recovery layer
&lt;/h2&gt;

&lt;p&gt;n8n's Server CLI can export all database entity types. Its documentation positions this tooling for backups and migrations, including moves between SQLite and PostgreSQL. Execution-history data tables are excluded by default because they can be large.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; node n8n &lt;span class="se"&gt;\&lt;/span&gt;
  n8n &lt;span class="nb"&gt;export&lt;/span&gt;:entities &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--outputDir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/node/.n8n/cli-backup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Workflow and credential exports are also useful for selective recovery or versioned copies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;n8n &lt;span class="nb"&gt;export&lt;/span&gt;:workflow &lt;span class="nt"&gt;--backup&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;backups/workflows/
n8n &lt;span class="nb"&gt;export&lt;/span&gt;:credentials &lt;span class="nt"&gt;--backup&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;backups/credentials/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Avoid decrypted credential exports for routine backups. n8n supports a &lt;code&gt;--decrypted&lt;/code&gt; flag for migrations to a different secret key, but the resulting files expose every sensitive value in plain text.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  A safe restore runbook
&lt;/h2&gt;

&lt;p&gt;First restore into an isolated instance with no production webhooks, schedules, email sends, payment calls, or CRM writes. The first recovery test should never overwrite the only production copy.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Record the target.&lt;/strong&gt; Use the same n8n version first, the same database type, and compatible custom-node versions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create fresh infrastructure.&lt;/strong&gt; Prepare a new Docker volume or empty PostgreSQL database rather than clearing production.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restore the original encryption key.&lt;/strong&gt; Put the protected &lt;code&gt;N8N_ENCRYPTION_KEY&lt;/code&gt; or original n8n settings in place before n8n reads the restored credentials.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restore the database.&lt;/strong&gt; Extract the stopped SQLite volume archive into the new volume, or restore the PostgreSQL dump into the new database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restore the surrounding state.&lt;/strong&gt; Reapply deployment settings, binary storage, custom nodes, public URL settings, and reverse-proxy configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Start without production traffic.&lt;/strong&gt; Keep DNS, proxy routing, and outbound side effects isolated while checking startup and migration logs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify the application.&lt;/strong&gt; Confirm login, projects, workflows, credentials, node availability, required execution history, and retained binary files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run one safe test.&lt;/strong&gt; Use test credentials or a non-destructive workflow and confirm that a credential can decrypt and authenticate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publish deliberately.&lt;/strong&gt; Review which workflows should be published before switching traffic.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Restore SQLite into a new volume
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker volume create n8n_restore_data
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; n8n_restore_data:/data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;/backups:/backup:ro"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  alpine &lt;span class="nb"&gt;tar &lt;/span&gt;xzf /backup/n8n-data-2026-07-18.tgz &lt;span class="nt"&gt;-C&lt;/span&gt; /data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Point a separate restore Compose file at &lt;code&gt;n8n_restore_data&lt;/code&gt;. Do not bind it to the production hostname or webhook route until the checklist passes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Restore PostgreSQL into a fresh database
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;createdb n8n_restore
pg_restore &lt;span class="nt"&gt;--dbname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;n8n_restore n8n-2026-07-18.dump
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Real deployments may need explicit host, user, role, ownership, TLS, and schema options. Test the exact command your operator will use and document it without embedding the password.&lt;/p&gt;

&lt;h2&gt;
  
  
  Restore-test checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The backup job has a timestamp, size, success state, and failure alert.&lt;/li&gt;
&lt;li&gt;The database and encryption key come from the same recoverable setup.&lt;/li&gt;
&lt;li&gt;The restore starts on a separate hostname, volume, and database.&lt;/li&gt;
&lt;li&gt;Credential nodes open without decryption errors and one test authentication succeeds.&lt;/li&gt;
&lt;li&gt;Required custom or community nodes load at the expected versions.&lt;/li&gt;
&lt;li&gt;Published workflows, schedules, and webhook paths are reviewed before traffic moves.&lt;/li&gt;
&lt;li&gt;Required retained binary files can be opened.&lt;/li&gt;
&lt;li&gt;The restore time and missing manual steps are recorded for the next test.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common backup failures
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mistake&lt;/th&gt;
&lt;th&gt;What breaks during recovery&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Only exporting workflow JSON&lt;/td&gt;
&lt;td&gt;The complete instance state is not restored&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database without the original key&lt;/td&gt;
&lt;td&gt;Credentials remain encrypted but cannot be used&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copying a live SQLite file casually&lt;/td&gt;
&lt;td&gt;The backup may not represent one consistent database state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skipping binary storage&lt;/td&gt;
&lt;td&gt;Retained documents or images may be missing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restoring straight over production&lt;/td&gt;
&lt;td&gt;A bad archive or wrong config can remove the working recovery path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Never testing the restore&lt;/td&gt;
&lt;td&gt;Missing keys, permissions, packages, and manual steps appear during the incident&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.n8n.io/hosting/installation/docker/" rel="noopener noreferrer"&gt;n8n Docker persistence and PostgreSQL guidance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.n8n.io/hosting/configuration/supported-databases-settings/" rel="noopener noreferrer"&gt;n8n supported databases and SQLite location&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.n8n.io/hosting/configuration/configuration-examples/encryption-key/" rel="noopener noreferrer"&gt;n8n encryption-key behavior&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.n8n.io/hosting/cli-commands/" rel="noopener noreferrer"&gt;n8n Server CLI export and import commands&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.n8n.io/hosting/scaling/binary-data/" rel="noopener noreferrer"&gt;n8n binary-data storage modes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/current/backup-dump.html" rel="noopener noreferrer"&gt;PostgreSQL SQL dump documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sqlite.org/backup.html" rel="noopener noreferrer"&gt;SQLite online backup documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://floxolab.com/blog/backup-restore-self-hosted-n8n" rel="noopener noreferrer"&gt;FloxoLab&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>selfhosted</category>
      <category>devops</category>
      <category>automation</category>
    </item>
    <item>
      <title>Retry Failed n8n API Requests Without Creating Duplicates</title>
      <dc:creator>Stepan Nikonov</dc:creator>
      <pubDate>Thu, 13 Aug 2026 04:15:40 +0000</pubDate>
      <link>https://dev.to/floxolab/retry-failed-n8n-api-requests-without-creating-duplicates-23bk</link>
      <guid>https://dev.to/floxolab/retry-failed-n8n-api-requests-without-creating-duplicates-23bk</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A timeout can hide a successful order, lead, payment, or message. The safe retry pattern uses one stable operation key, retries only temporary failures, and records the confirmed result.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Turning on &lt;strong&gt;Retry On Fail&lt;/strong&gt; in n8n does not prevent duplicates by itself. It repeats the failed node. If an API completed the first request but its response never reached n8n, repeating a POST with a new key can create the same object twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decide whether the request is safe to repeat
&lt;/h2&gt;

&lt;p&gt;HTTP defines GET, HEAD, OPTIONS, and TRACE as safe methods. PUT, DELETE, and the safe methods are idempotent by their intended semantics. POST is not automatically idempotent.&lt;/p&gt;

&lt;p&gt;Real APIs add their own rules, so the provider's documentation remains the contract. A DELETE endpoint may be harmless to repeat, while a POST that sends an email, creates a payment, or reserves stock needs explicit duplicate protection.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Failure&lt;/th&gt;
&lt;th&gt;Default action&lt;/th&gt;
&lt;th&gt;Duplicate risk&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Network timeout or reset&lt;/td&gt;
&lt;td&gt;Retry the identical request with the same operation key&lt;/td&gt;
&lt;td&gt;High, because the remote service may already have committed the action&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;429 Too Many Requests&lt;/td&gt;
&lt;td&gt;Wait for the provider's limit window, then retry&lt;/td&gt;
&lt;td&gt;Controlled only if the operation remains idempotent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;500, 502, 503, or 504&lt;/td&gt;
&lt;td&gt;Use a small bounded retry policy when the provider permits it&lt;/td&gt;
&lt;td&gt;Potentially high for state-changing requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;400, 401, 403, or most 404s&lt;/td&gt;
&lt;td&gt;Fix the request, credentials, permission, or resource first&lt;/td&gt;
&lt;td&gt;Blind retries rarely fix the cause&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;409 Conflict&lt;/td&gt;
&lt;td&gt;Inspect the API-specific error before deciding&lt;/td&gt;
&lt;td&gt;May indicate an existing operation or key conflict&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Step 1: create one key for one business operation
&lt;/h2&gt;

&lt;p&gt;Build the key before the HTTP Request node from an ID that already belongs to the event: an order ID, checkout ID, lead ID, invoice ID, or source event ID. Include the action name so different operations on the same record cannot collide.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;={{ 'create-order:' + $json.orderId }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep this value unchanged across automatic node retries, manual execution retries, and later recovery runs. Do not use &lt;code&gt;$now&lt;/code&gt;, the execution ID, or a freshly generated random value on each attempt. Those identify the attempt, not the operation, so the destination sees every retry as new work.&lt;/p&gt;

&lt;p&gt;The key must also stay paired with the same endpoint and payload. If you correct the amount, recipient, or other business data, treat that as a deliberate new operation according to the provider's rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: use the API's idempotency feature first
&lt;/h2&gt;

&lt;p&gt;If the destination API supports idempotency, send the stable key in the exact header or field its documentation specifies. Stripe, for example, accepts an &lt;code&gt;Idempotency-Key&lt;/code&gt; header for POST requests and returns the stored result when the same request is repeated with the same key.&lt;/p&gt;

&lt;p&gt;That header name is not universal. Some APIs use a request ID, client reference, deduplication ID, or provider-specific field.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the HTTP Request node and add the provider's documented idempotency header.&lt;/li&gt;
&lt;li&gt;Map the stable operation key as its value.&lt;/li&gt;
&lt;li&gt;Keep the method, URL, and request body identical on every retry.&lt;/li&gt;
&lt;li&gt;Save the external object ID from the confirmed response for later reconciliation.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Adding an &lt;code&gt;Idempotency-Key&lt;/code&gt; header only works when the destination implements that contract. An unknown header may simply be ignored.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 3: configure a bounded n8n retry
&lt;/h2&gt;

&lt;p&gt;Every n8n node has a &lt;strong&gt;Retry On Fail&lt;/strong&gt; setting. When enabled, n8n reruns the node after a failure until it succeeds or reaches the configured attempt limit. For a known transient endpoint, start with a small policy such as three tries and a short wait between tries. Increase it only when the provider's limit and incident behavior justify the extra traffic.&lt;/p&gt;

&lt;p&gt;This built-in setting is best when every error produced by that node is safe to retry. If you need status-aware behavior, enable &lt;strong&gt;Include Response Headers and Status&lt;/strong&gt; and &lt;strong&gt;Never Error&lt;/strong&gt; in the HTTP Request node. The node can then pass the status code and headers to an IF or Switch branch instead of stopping on every non-2xx response.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Send 2xx responses to the success branch.&lt;/li&gt;
&lt;li&gt;Send 429 and approved temporary 5xx responses to a Wait step and bounded retry path.&lt;/li&gt;
&lt;li&gt;Send authentication, validation, permission, and unexpected responses to a review or alert branch.&lt;/li&gt;
&lt;li&gt;Stop after the maximum attempt count. Never create an unbounded loop.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 4: slow down 429 retries
&lt;/h2&gt;

&lt;p&gt;A 429 response means the client sent too many requests in the provider's current limit window. Some APIs include &lt;code&gt;Retry-After&lt;/code&gt; as seconds or an HTTP date. Read and honor it when the provider documents that behavior. If the header is absent, use the API's published limit and increase the delay between attempts rather than retrying immediately.&lt;/p&gt;

&lt;p&gt;The HTTP Request node also has &lt;strong&gt;Items per Batch&lt;/strong&gt; and &lt;strong&gt;Batch Interval&lt;/strong&gt; options. They control how many input items are sent in each batch and how many milliseconds n8n waits between batches. Use them to prevent a known burst from creating the 429 in the first place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: add a persistent duplicate check when needed
&lt;/h2&gt;

&lt;p&gt;When the API has no idempotency feature, keep a small request ledger. n8n's Data Table node can check whether a row exists, insert rows, update them, and upsert by conditions.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;request_key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stable ID for the business operation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;pending&lt;/code&gt;, &lt;code&gt;succeeded&lt;/code&gt;, &lt;code&gt;failed&lt;/code&gt;, or &lt;code&gt;unknown&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;external_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ID returned by the destination when available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;updated_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Timestamp for recovery and stale-pending review&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Use this sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check for a row with the request key.&lt;/li&gt;
&lt;li&gt;If it already says &lt;code&gt;succeeded&lt;/code&gt;, return the stored result and skip the API call.&lt;/li&gt;
&lt;li&gt;If no row exists, record &lt;code&gt;pending&lt;/code&gt;, then call the API.&lt;/li&gt;
&lt;li&gt;On a confirmed 2xx response, update the row to &lt;code&gt;succeeded&lt;/code&gt; and store the external ID.&lt;/li&gt;
&lt;li&gt;On a timeout, keep the state as &lt;code&gt;unknown&lt;/code&gt; until you query the destination or retry safely.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This ledger is useful for sequential or low-concurrency workflows, but a check followed by an insert is not an atomic lock. Two executions can check at the same moment and both see no row. For parallel production traffic, prefer destination-side idempotency or a database table with a unique constraint on &lt;code&gt;request_key&lt;/code&gt; and an atomic claim operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not use workflow static data as a production lock
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;$getWorkflowStaticData()&lt;/code&gt; helper looks convenient for storing processed IDs, but n8n labels it experimental, says the data should be small, and warns that it may behave unreliably under high-frequency executions. That makes it unsuitable as the main duplicate barrier for valuable orders, payments, leads, or outbound messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  A production-safe request sequence
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Validate that the event contains a stable source ID.&lt;/li&gt;
&lt;li&gt;Create the operation key once.&lt;/li&gt;
&lt;li&gt;Check any local success record.&lt;/li&gt;
&lt;li&gt;Send the request with the provider's idempotency mechanism.&lt;/li&gt;
&lt;li&gt;Retry only network failures, 429s, and provider-approved temporary errors.&lt;/li&gt;
&lt;li&gt;Reuse the same key and payload for every attempt.&lt;/li&gt;
&lt;li&gt;Store the external ID after confirmed success.&lt;/li&gt;
&lt;li&gt;Route exhausted or uncertain outcomes to an Error Workflow or manual review queue.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you do not yet know why the node failed, use the &lt;a href="https://floxolab.com/blog/n8n-debug-failed-workflows" rel="noopener noreferrer"&gt;failed n8n workflow debugging guide&lt;/a&gt;. Once the retry budget is exhausted, use an &lt;a href="https://floxolab.com/blog/n8n-error-workflow-telegram-alerts" rel="noopener noreferrer"&gt;n8n Error Workflow&lt;/a&gt; to send the failure to Telegram, Slack, Google Chat, email, or another incident channel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.n8n.io/workflows/components/nodes/" rel="noopener noreferrer"&gt;n8n node settings and Retry On Fail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.httprequest/" rel="noopener noreferrer"&gt;n8n HTTP Request node options&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.datatable/rows/" rel="noopener noreferrer"&gt;n8n Data Table row operations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.n8n.io/code/cookbook/builtin/get-workflow-static-data/" rel="noopener noreferrer"&gt;n8n workflow static data limitations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9110.html#name-idempotent-methods" rel="noopener noreferrer"&gt;RFC 9110 idempotent method semantics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.stripe.com/api/idempotent_requests" rel="noopener noreferrer"&gt;Stripe idempotent requests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Retry-After" rel="noopener noreferrer"&gt;Retry-After response header&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://floxolab.com/blog/n8n-retry-failed-api-requests-without-duplicates" rel="noopener noreferrer"&gt;FloxoLab&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>automation</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Product Photo to WooCommerce Listing with n8n and AI Vision</title>
      <dc:creator>Stepan Nikonov</dc:creator>
      <pubDate>Tue, 11 Aug 2026 05:50:47 +0000</pubDate>
      <link>https://dev.to/floxolab/product-photo-to-woocommerce-listing-with-n8n-and-ai-vision-1e2b</link>
      <guid>https://dev.to/floxolab/product-photo-to-woocommerce-listing-with-n8n-and-ai-vision-1e2b</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A private admin chat workflow for ecommerce: send a product photo, analyze the image, create a WooCommerce listing, attach the product image, and get the product URL back for review.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a small product-intake workflow, not a promise that AI can run an ecommerce store by itself. The useful part is narrower: a product photo becomes a reviewable WooCommerce product page without a person opening WordPress, uploading the image, writing the first description, and sending the review link by hand.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Photo in, review link out.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The workflow handles the repetitive first pass. A person can still check the title, price, category, and claims before using it in a real store.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;AI step&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Product photo in a private admin chat&lt;/td&gt;
&lt;td&gt;Vision analysis creates listing fields&lt;/td&gt;
&lt;td&gt;WooCommerce product URL for review&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The generated product page
&lt;/h2&gt;

&lt;p&gt;The result is a real WooCommerce product page in a temporary test store. For this demo, the workflow creates the product with the uploaded image, a short title, a short description, and a demo price. The screenshot still shows the store's default category because category mapping was not part of this prototype.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjuuiqqzbfv3itfr9atts.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjuuiqqzbfv3itfr9atts.png" alt="WooCommerce product page generated from a product photo, with product image, title, default category, and short description" width="800" height="642"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The generated WooCommerce page includes the uploaded image, product title, demo price, and short description.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The loop closes in the same chat
&lt;/h2&gt;

&lt;p&gt;The operator sends a product photo into a private admin chat. After n8n creates the WooCommerce listing, the bot replies with the product URL so the person can inspect it immediately. Telegram is just the internal intake tool here, not a public FloxoLab contact channel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F44dbiinacih3kbay6w9e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F44dbiinacih3kbay6w9e.png" alt="Private admin chat showing a product photo sent in and a WooCommerce product link returned by the workflow" width="463" height="527"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The same private admin chat closes the loop: the product photo comes in, and the WooCommerce product URL comes back.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The workflow map
&lt;/h2&gt;

&lt;p&gt;The n8n canvas stays simple: receive the photo, fetch the Telegram file, upload it to WordPress Media, analyze the image, create the WooCommerce product, and send a message back to the chat.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmd8gw5l5a1qce3i464bz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmd8gw5l5a1qce3i464bz.png" alt="n8n workflow map for Telegram image intake, WordPress Media upload, AI vision analysis, WooCommerce product creation, and Telegram notification" width="800" height="297"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The workflow connects a private admin chat to WordPress Media, AI vision, WooCommerce product creation, and a return message.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI and tool setup
&lt;/h2&gt;

&lt;p&gt;The image analysis step describes the visible product details. The AI agent then uses that analysis to prepare the product fields and call the WooCommerce tool. In this demo, the price is fixed and the text fields are intentionally short.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvespt4n28kid16fbqlud.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvespt4n28kid16fbqlud.png" alt="n8n AI Agent and WooCommerce tool setup for creating a product listing from product image analysis" width="800" height="441"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The AI agent turns image analysis into concise WooCommerce product fields.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this works for WooCommerce teams
&lt;/h2&gt;

&lt;p&gt;This approach works well for WooCommerce because the store already has the pieces the workflow needs: media uploads, product creation, product status, categories, and product URLs. n8n can talk to WooCommerce through native nodes, so a small team can test a product-intake workflow without building a custom admin panel first.&lt;/p&gt;

&lt;p&gt;For this demo I used a temporary WordPress and WooCommerce test store. That made it easy to validate the workflow quickly before thinking about a real client store.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the AI is allowed to do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use image facts&lt;/strong&gt; — Listing text should come from the image analysis, not invented product claims.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep fields short&lt;/strong&gt; — The title and description stay compact so a person can review them quickly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use demo defaults&lt;/strong&gt; — This prototype uses a fixed demo price. A real store should pull price, stock, SKU, and category from a trusted source.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoid fake details&lt;/strong&gt; — No sizes, materials, shipping promises, inventory, or brand claims should be added unless they are provided by the store.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Good fits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;WooCommerce stores&lt;/strong&gt; — Small shops that still create product pages manually from product photos or supplier images.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ecommerce agencies&lt;/strong&gt; — Teams onboarding new product lines that need a faster first draft before final merchandising review.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Catalog operations&lt;/strong&gt; — Teams that repeatedly turn image inputs into product records, review queues, or marketplace drafts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Admin chat intake&lt;/strong&gt; — The intake chat could be Telegram, Messenger, or Viber depending on the team's daily tools. The important part is private operator intake, not a public support chatbot.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I would improve next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Draft first&lt;/strong&gt; — Create products as &lt;code&gt;draft&lt;/code&gt; by default, then publish after approval.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Approval button&lt;/strong&gt; — Add a review step so the operator can approve, edit, or reject a generated product before it goes live.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better product data&lt;/strong&gt; — Map SKU, category, price, stock, size, color, and supplier source from trusted tables instead of asking AI to infer them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate checks&lt;/strong&gt; — Search existing products before creating a new page, especially when supplier photos repeat across batches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clear failures&lt;/strong&gt; — If a message has no photo, the file cannot be downloaded, or the image analysis is weak, send a clear error back to the chat.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Related guide
&lt;/h2&gt;

&lt;p&gt;This build leans on an AI agent node rather than a fixed script. For the difference between chatbots, automation workflows, and agents, read &lt;a href="https://floxolab.com/blog/ai-agents-for-business" rel="noopener noreferrer"&gt;AI Agents for Business: What They Actually Do (and 3 Ways to Use Them)&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;This workflow is useful because it turns a product photo into a reviewable WooCommerce listing without building a custom admin interface. A person should still check the title, category, price, and product claims before using it in a real store, but the blank-page work is already done: image upload, first draft, product page, and review link are prepared automatically.&lt;/p&gt;

&lt;p&gt;Even with human review, the workflow can save the repetitive first pass: uploading the image, opening WooCommerce, creating the product, writing a short description, and sending the page link to the person responsible for approval.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://floxolab.com/cases/product-photo-woocommerce-listing-n8n" rel="noopener noreferrer"&gt;FloxoLab&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>ai</category>
      <category>ecommerce</category>
      <category>automation</category>
    </item>
    <item>
      <title>Form + AI Chat Intake with n8n</title>
      <dc:creator>Stepan Nikonov</dc:creator>
      <pubDate>Sun, 09 Aug 2026 15:05:30 +0000</pubDate>
      <link>https://dev.to/floxolab/form-ai-chat-intake-with-n8n-4mia</link>
      <guid>https://dev.to/floxolab/form-ai-chat-intake-with-n8n-4mia</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A practical workflow that turns a vague website message into something a human can actually act on: a lead record, a private alert, and a workflow map email.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This could have been a dramatic automation story about one workflow saving a business 150,000 hours. It is not. This is a practical demo of a smaller, more common problem: turning a vague website message into something a human can actually act on.&lt;/p&gt;

&lt;p&gt;The workflow uses the same intake surfaces FloxoLab already has on the site: a contact form for structured requests and an AI chat for people who start with a messy question.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The form collects fields. The chat collects context.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The useful workflow is not the chatbot by itself. It is the handoff: what gets saved, who gets notified, and what the human can do next.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Inputs&lt;/th&gt;
&lt;th&gt;AI step&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Contact form and AI chat&lt;/td&gt;
&lt;td&gt;Groq returns JSON&lt;/td&gt;
&lt;td&gt;Lead brief and map email&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Real tools, test data, and why that matters
&lt;/h2&gt;

&lt;p&gt;This workflow shows the working intake pattern: validation, AI response, lead record, private alert, and follow-up email.&lt;/p&gt;

&lt;p&gt;Production versions usually need more testing, cleaner edge-case handling, more careful copy, and fields that match the team's real sales or support process. A demo proves the path. Production makes it boring enough to trust.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftksxvxeswsw5tnrju94p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftksxvxeswsw5tnrju94p.png" alt="n8n workflow overview showing contact form and AI chat intake paths" width="799" height="486"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The workflow has two entry points: a structured contact form path and a conversational AI chat path.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Start with the boring form path
&lt;/h2&gt;

&lt;p&gt;The contact form is the clean path. It already has the fields a human needs: name, email, current tools, budget range, and a short message. The workflow checks whether the email already exists, creates a Notion lead if it is new, sends a private alert, and returns a simple OK response.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw18kn1t2a9roxuwpdpex.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw18kn1t2a9roxuwpdpex.png" alt="FloxoLab website contact form with name email tools and budget fields" width="596" height="503"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The form gives the workflow structured fields before any AI is involved.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp0zo0uluqlg2cx9gums5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp0zo0uluqlg2cx9gums5.png" alt="n8n contact form path with duplicate check Notion lead and private alert" width="799" height="322"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The form path is intentionally simple: check duplicate, create lead, notify, respond.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Let the chat handle messy first messages
&lt;/h2&gt;

&lt;p&gt;The AI chat is for the person who does not know what to put in a form yet. It validates the message, keeps a short safe history, sends a compact instruction set to Groq, and expects a JSON response with reply text, email-offer state, and optional plan data.&lt;/p&gt;

&lt;p&gt;The AI prompt is not magic. It is a set of instructions that can be rewritten when the first version does not work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqf5cejb79r9vv13ctzek.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqf5cejb79r9vv13ctzek.png" alt="FloxoLab AI chat intake collecting context from a website visitor" width="575" height="683"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The chat asks for useful context, then offers to send a workflow map by email.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9w7pw8hhdatzgk5odovt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9w7pw8hhdatzgk5odovt.png" alt="n8n AI chat path with validation message builder Groq API and reply extraction" width="799" height="286"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The AI path is narrow: validate, build messages, call Groq, extract a safe reply.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Decide when the workflow should act
&lt;/h2&gt;

&lt;p&gt;The decision point is deliberately plain. If the model says the email is ready and the user has provided enough context, the workflow checks for duplicates, creates a lead, builds an email, and sends it. If not, it simply returns the chat reply.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F06helxyk8quapu38c70f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F06helxyk8quapu38c70f.png" alt="n8n decision point for sending a workflow map email or returning a chat reply" width="800" height="418"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The workflow should not create records or send emails just because a model replied. It needs an explicit state.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Give the human something useful
&lt;/h2&gt;

&lt;p&gt;The useful handoff is not "a lead arrived." It is a lead record with enough context, a private alert that tells the builder what happened, and a first-pass workflow map the user can reply to.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9yle5n59ev1zls2nglfq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9yle5n59ev1zls2nglfq.png" alt="Notion lead database record created from the intake workflow" width="800" height="232"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The Notion table can stay tiny or grow into a fuller CRM with source, status, budget, urgency, owner, and next action fields.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4sm3jouoyoneyxvtz5bf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4sm3jouoyoneyxvtz5bf.png" alt="Private admin alert for a new AI chat lead" width="401" height="183"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The alert can go to Slack, email, or a private internal channel. The important part is that the right person sees it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj5g92scc9gcj7lxl7myj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj5g92scc9gcj7lxl7myj.png" alt="Workflow map email sent to the user after AI chat intake" width="666" height="1064"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The email is a first-pass map, not a final quote: outcome, tools, steps, build range, monthly cost notes, and what to correct.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo vs production
&lt;/h2&gt;

&lt;p&gt;A demo can prove the path in half a day. A production workflow needs better copy, fallback paths, duplicate handling, error alerts, cleaner logs, and privacy-safe fields.&lt;/p&gt;

&lt;p&gt;Sometimes the hardest production bug is remembering to remove "This message was sent automatically with n8n." That sounds small, but it is exactly the kind of polish that separates a working demo from a workflow a business can comfortably use.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Fields are flexible.&lt;/strong&gt; The Notion database can have five fields or twenty-five: source, budget, tool stack, urgency, owner, status, next action, or whatever the handoff needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alerts are flexible.&lt;/strong&gt; The notification can go to Slack, email, Telegram privately, a CRM task, or the channel the team actually checks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The AI behavior is flexible.&lt;/strong&gt; It can ask one question, collect missing fields, draft the first reply, or stop and ask a human to review. The prompt is editable.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What can be customized
&lt;/h2&gt;

&lt;p&gt;I can spend 30 days trying to design the perfect intake workflow on paper, or build several working versions in half a day and learn from real messages. For small automations, the second path is usually more useful.&lt;/p&gt;

&lt;p&gt;The CRM can be Notion, Airtable, HubSpot, Google Sheets, or something else. The email can be plain text or formatted. The AI model can be Groq, OpenAI, &lt;a href="https://floxolab.com/blog/claude-connectors-ai-automation-philippines" rel="noopener noreferrer"&gt;Claude&lt;/a&gt;, or no AI at all if the form fields are enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;This is the kind of workflow automation Philippines teams can inspect: visible inputs, validation before action, AI that returns structured data, a human-readable lead record, and a clear next step.&lt;/p&gt;

&lt;p&gt;It is not a giant AI sales machine. It is a small intake workflow that makes the first human response easier.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://floxolab.com/cases/form-ai-chat-intake-n8n" rel="noopener noreferrer"&gt;FloxoLab&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>ai</category>
      <category>automation</category>
      <category>nocode</category>
    </item>
    <item>
      <title>AI Email Router with n8n</title>
      <dc:creator>Stepan Nikonov</dc:creator>
      <pubDate>Fri, 07 Aug 2026 16:20:35 +0000</pubDate>
      <link>https://dev.to/floxolab/ai-email-router-with-n8n-1hbi</link>
      <guid>https://dev.to/floxolab/ai-email-router-with-n8n-1hbi</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A practical n8n workflow that reads Gmail, normalizes the fields, adds basic rule signals, classifies the email with Groq, validates the result, routes a lead into a CRM sheet, alerts Slack, and logs the operation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This workflow shows the practical parts of AI email routing in the Philippines: the input, the rules, the classifier, the validation step, the route, and the log.&lt;/p&gt;

&lt;p&gt;The lead path is walked through in detail because it is enough to show the pattern. Support, invoice, spam, and review branches use the same structure: validate first, route second, act third, log last.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The slow part is not connecting nodes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is deciding what should happen, testing real emails, tuning labels, and making the workflow understandable enough for someone else to own.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Trigger&lt;/th&gt;
&lt;th&gt;AI step&lt;/th&gt;
&lt;th&gt;Detailed path&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unread Gmail message&lt;/td&gt;
&lt;td&gt;Groq classifier returns JSON&lt;/td&gt;
&lt;td&gt;Lead -&amp;gt; CRM -&amp;gt; Slack -&amp;gt; log&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The full workflow map
&lt;/h2&gt;

&lt;p&gt;The canvas has five possible routes. This page follows the lead route end to end, while keeping the other branches visible on the map.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyqn5aidhragtr0l6r1sb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyqn5aidhragtr0l6r1sb.png" alt="n8n workflow canvas for an AI Email Router" width="800" height="419"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The workflow uses rules, AI classification, validation, routing, action nodes, and one master Google Sheets log.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Start with a controlled test email
&lt;/h2&gt;

&lt;p&gt;Before building the rest of the workflow, send yourself one test email for the scenario you want to prove. In this case, the test email is a lead inquiry asking about n8n automation services and pricing.&lt;/p&gt;

&lt;p&gt;The Gmail Trigger is then tested and pinned. That gives every next node a stable input while the workflow is being built, instead of waiting for Gmail to trigger again.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frh7yu9ytsopoy0s0r4gv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frh7yu9ytsopoy0s0r4gv.png" alt="Gmail Trigger node with pinned test event data" width="799" height="430"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The first checkpoint is simple: confirm that Gmail exposes the message id, subject, sender, snippet, labels, and payload.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Normalize fields once
&lt;/h2&gt;

&lt;p&gt;Gmail gives useful data, but it is not shaped for the rest of the workflow. The Extract Fields node turns raw Gmail output into clean top-level fields: sender, sender name, domain, subject, body snippet, attachment names, MIME types, and a PDF flag.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fudr8wmte9al2uyzurdij.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fudr8wmte9al2uyzurdij.png" alt="Extract Fields n8n code node" width="800" height="427"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Normalize once, then every later node can use clean expressions like sender, domain, subject, and has_pdf.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Add basic rule signals before AI
&lt;/h2&gt;

&lt;p&gt;Basic Rules does not replace the classifier in this demo. It adds cheap, inspectable signals before the AI step: suspicious domains, no-reply senders, newsletter patterns, aggressive spam words, trusted domains, and attachment flags.&lt;/p&gt;

&lt;p&gt;That makes the workflow easier to review. If the model later calls something spam, validation can check whether the rule-based signals agree before archiving anything.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F89bkzy8obfqa46o6ye1e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F89bkzy8obfqa46o6ye1e.png" alt="Basic Rules n8n code node with spam and marketing signals" width="799" height="428"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The rules are intentionally editable. A real team would tune known spam domains, newsletter senders, and trusted domains over time.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Keep the AI classifier small
&lt;/h2&gt;

&lt;p&gt;The classifier is called through an HTTP Request to Groq. Its job is narrow: return one label, one confidence score, and one short reason.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "label": "lead",
  "confidence": 0.95,
  "reason": "The email is an inquiry about services and pricing from a potential client."
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5lox2pdr0z7zkqktg3td.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5lox2pdr0z7zkqktg3td.png" alt="AI Classifier HTTP Request node for Groq" width="800" height="427"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The AI does not create tickets, archive email, or decide the final business action. It only returns structured classification data.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Validate before routing
&lt;/h2&gt;

&lt;p&gt;AI output is not safe to trust directly. It can return markdown, broken JSON, a low confidence value, or a label that is semantically correct but operationally unsafe.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Invalid JSON goes to review.&lt;/strong&gt; The workflow should not fall over because the model wrapped JSON in markdown.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Confidence below 0.75 goes to review.&lt;/strong&gt; If the model is unsure, a human should see it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Spam needs rule confirmation.&lt;/strong&gt; A spam label alone is not enough to archive a message.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Invoice without PDF goes to review.&lt;/strong&gt; The action depends on attachments, not only on the AI label.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  6. Route by validated route, not raw AI label
&lt;/h2&gt;

&lt;p&gt;The router uses the validated route field. This keeps the Switch node simple: the safety decisions already happened in Validation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl8repjduhtju49l2vxhv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl8repjduhtju49l2vxhv.png" alt="Routing Rules switch node in n8n" width="800" height="426"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Routes include support, lead, invoice, spam archive, and human review.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Lead path: CRM, Slack, and master log
&lt;/h2&gt;

&lt;p&gt;The tested path for this demo is lead routing. The workflow writes a CRM row, posts a Slack alert, and then appends the operation to the master log.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1yekcvyk2ci38s9qm8s8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1yekcvyk2ci38s9qm8s8.png" alt="Add to CRM Google Sheets node" width="800" height="422"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Google Sheets receives the sender name, email, domain, subject, source, status, and created timestamp.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft1j8px7o6qaog3t35nz3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft1j8px7o6qaog3t35nz3.png" alt="Slack message for a new lead" width="800" height="690"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Slack gets a readable lead alert with the classification reason and confidence.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyu0zn4eedk852vobxnqi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyu0zn4eedk852vobxnqi.png" alt="CRM Leads Google Sheet after the workflow runs" width="799" height="544"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The CRM sheet is intentionally plain. For many small workflows, a clean table is enough for the first version.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkm0wlf2jxk7artjrac9a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkm0wlf2jxk7artjrac9a.png" alt="Email Router Log Google Sheet after the workflow runs" width="800" height="307"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The master log records completed routes: message id, timestamp, sender, label, confidence, reason, route, and action.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwjdum7nheu108trsgt3c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwjdum7nheu108trsgt3c.png" alt="Master Log Google Sheets node in n8n" width="800" height="435"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;This demo log records completed workflow routes. A production version should add a separate Error Trigger workflow for failed executions.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Other branches use the same pattern
&lt;/h2&gt;

&lt;p&gt;Fully testing every branch means preparing separate tables, statuses, dates, files, review rows, and sometimes real attachments. That setup work matters more than simply drawing lines between nodes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;support&lt;/strong&gt; — Create a Notion or Airtable ticket, draft a reply, notify Slack, then log the completed route.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;invoice&lt;/strong&gt; — Save the PDF to Drive, add a row to an invoice sheet, notify admin, then log the route. If there is no PDF, send it to review.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;spam&lt;/strong&gt; — Archive only when rule signals and confidence agree. Otherwise send it to review.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;review&lt;/strong&gt; — Add uncertain messages to a human review queue instead of forcing the workflow to guess.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What this demo does not cover
&lt;/h2&gt;

&lt;p&gt;This is a useful first version, not the final production checklist. A production build would add an Error Trigger workflow, retry rules, stricter credential ownership, more test messages, and clearer handoff notes for the person who owns the inbox.&lt;/p&gt;

&lt;p&gt;It would also test each branch with real examples: a support question, a real invoice PDF, a marketing email, a spam candidate, and at least one messy message that should go to review.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The setup pieces can be scaffolded too, such as a Drive folder, Sheets logs, a CRM sheet, and a Notion ticket database. I covered that pattern in the &lt;a href="https://floxolab.com/blog/claude-connectors-ai-automation-philippines" rel="noopener noreferrer"&gt;Claude connectors guide&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;This is the kind of workflow automation Philippines teams can inspect: one trigger, clean fields, editable rules, a small AI classifier, validation before action, visible branches, Slack alerts, and a master log.&lt;/p&gt;

&lt;p&gt;It is not AI magic. It is a small operations workflow that becomes useful when the labels, thresholds, and ownership are tuned against real email.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://floxolab.com/cases/ai-email-router-n8n" rel="noopener noreferrer"&gt;FloxoLab&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>ai</category>
      <category>automation</category>
      <category>email</category>
    </item>
    <item>
      <title>Screaming Frog Review (2026): The Technical SEO Crawler I Audit With</title>
      <dc:creator>Stepan Nikonov</dc:creator>
      <pubDate>Thu, 06 Aug 2026 01:40:10 +0000</pubDate>
      <link>https://dev.to/floxolab/screaming-frog-review-2026-the-technical-seo-crawler-i-audit-with-1m1l</link>
      <guid>https://dev.to/floxolab/screaming-frog-review-2026-the-technical-seo-crawler-i-audit-with-1m1l</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Screaming Frog is the tool I actually open to run a technical SEO audit, including the one on this site. It is not pretty and it is not for everyone, but for finding what is broken under the hood of a website, nothing else comes close at the price. Here is the full review: what it finds, the free version versus the paid licence, where it frustrates, and who really needs it.&lt;/p&gt;

&lt;p&gt;4.6 out of 5&lt;/p&gt;

&lt;p&gt;The short verdict&lt;/p&gt;

&lt;p&gt;The industry-standard desktop crawler for technical SEO, and the best value in its class. It finds the template-level problems other tools miss, the free version covers small sites, and the licence is cheap for what it does. It is single-purpose, runs on your own machine, and has a learning curve, so it is a specialist's tool, not an all-in-one.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most SEO tools show you a dashboard. Screaming Frog shows you the truth. It is a desktop crawler: you point it at a website and it walks every link the way a search engine would, then hands you a spreadsheet of everything it found, every URL, status code, title, redirect, canonical, and broken link. It is the difference between a tool telling you "your site health is 82" and a tool telling you "these 14 pages return 404, these 6 titles are duplicated, and this canonical points to the wrong URL". For the technical and on-page work I do, that second answer is the one I need.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Screaming Frog actually does
&lt;/h2&gt;

&lt;p&gt;It crawls, and it crawls thoroughly. The things I reach for it for, over and over:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Broken links and redirects.&lt;/strong&gt; Every 404, every redirect chain, every loop, listed with the page it sits on. This alone is worth the download.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Titles, meta, and headings at scale.&lt;/strong&gt; Missing, duplicate, too long, or too short, across the whole site at once, which is exactly the template-level problem a dashboard tool hides.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Canonicals, indexability, and directives.&lt;/strong&gt; What is canonicalised where, what is noindexed, what robots and the meta tags actually say, so you can see why a page is or is not in the index.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structured data and hreflang.&lt;/strong&gt; Validates schema and international tags page by page, the kind of detail covered in &lt;a href="https://floxolab.com/seo/guides/technical-seo-fixes-agencies-wont-touch" rel="noopener noreferrer"&gt;the technical SEO fixes agencies won't touch&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;XML sitemaps and crawl depth.&lt;/strong&gt; Generate a sitemap, or see how many clicks deep your important pages sit, which affects how Google crawls them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integrations and rendering (paid).&lt;/strong&gt; Pull in Google Analytics, Search Console, and PageSpeed data against each URL, render JavaScript, schedule crawls, and extract anything from the HTML with custom XPath.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Free versus paid: what you actually get
&lt;/h2&gt;

&lt;p&gt;Here is what you actually get for each. Treat the prices as a snapshot from June 2026, and check the live page before buying.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Crawl limit&lt;/th&gt;
&lt;th&gt;Advanced features&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;500 URLs&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Paid licence&lt;/td&gt;
&lt;td&gt;~$279 / £199 / €245 per year&lt;/td&gt;
&lt;td&gt;Unlimited&lt;/td&gt;
&lt;td&gt;Yes (JS rendering, scheduling, integrations, custom extraction)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 500-URL free limit is more generous than it sounds. A typical small business site is well under 500 pages, so for a single small site, a spot check, or learning the tool, the free version genuinely does the job and costs nothing. You hit the wall when you crawl larger sites, audit many sites, or need JavaScript rendering and the API integrations. At that point the licence, per user per year, is one of the best-value purchases in SEO. The licence is per person, so a two-person team needs two.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strengths and weak spots
&lt;/h2&gt;

&lt;p&gt;It earns its 4.6, not a 5. Here is the balance, the strengths alongside the real frustrations.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it does well
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The deepest, most trustworthy technical crawl at this price.&lt;/li&gt;
&lt;li&gt;Free version covers small sites with no catch beyond the 500-URL cap.&lt;/li&gt;
&lt;li&gt;Finds template-level issues dashboards hide.&lt;/li&gt;
&lt;li&gt;Integrations, rendering, and custom extraction on the paid tier.&lt;/li&gt;
&lt;li&gt;No subscription lock-in to a web platform; the data is yours, locally.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Where it gives ground
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Single-purpose: no rank tracking, no keyword research.&lt;/li&gt;
&lt;li&gt;Steep learning curve; the interface is dense and unapologetically technical.&lt;/li&gt;
&lt;li&gt;Desktop app: large crawls eat RAM and need memory configuration.&lt;/li&gt;
&lt;li&gt;You read and interpret the data yourself; it does not hand you a prioritized plan.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last point is the one that matters for a non-specialist. Screaming Frog tells you everything, which is exactly the problem if you do not already know what to ignore. It is a scalpel, not a checkup. It will happily show a small business owner ten thousand rows and no sense of which three actually hurt rankings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should use it, and who should not
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use Screaming Frog if&lt;/strong&gt; you do technical SEO yourself, as a freelancer, agency, in-house SEO, or a developer who wants the real state of a site. The free version is the right first download for a small site; the licence pays for itself fast once you audit regularly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Look elsewhere if&lt;/strong&gt; you want rank tracking, keyword research, or reporting, which Screaming Frog does not do; for that, an all-in-one like &lt;a href="https://floxolab.com/seo/tools/se-ranking-review" rel="noopener noreferrer"&gt;SE Ranking&lt;/a&gt; fits better, and many people run both. And if you are a small business owner who just wants the problems found and fixed once, without learning a crawler, buying the tool is the wrong purchase. That is what an audit is for.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bottom line
&lt;/h2&gt;

&lt;p&gt;Screaming Frog is the technical SEO crawler I trust and the one I run when I audit a site, this one included. For specialists it is close to essential, and cheap for what it does. For a small business owner it is a powerful free spot-check on a small site, and a poor purchase if you are not going to live in the data. Know which of those you are, and the decision is easy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is Screaming Frog free?
&lt;/h3&gt;

&lt;p&gt;Yes, with a limit. The free version of the Screaming Frog SEO Spider crawls up to 500 URLs, which is enough for a small site or a quick spot check. To crawl more than 500 URLs and unlock advanced features like JavaScript rendering, scheduling, and API integrations, you need the paid licence.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much does Screaming Frog cost?
&lt;/h3&gt;

&lt;p&gt;The paid licence is GBP 199, USD 279, or EUR 245 per year, per user, at the time of writing. The licence is annual and per person, so two people need two licences, with small discounts on bulk purchases of five or more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Screaming Frog worth it for a small business?
&lt;/h3&gt;

&lt;p&gt;For most small business owners, the free 500-URL version is enough for an occasional check, and you do not need the paid licence. The licence is worth it for anyone auditing sites regularly: freelancers, agencies, and in-house SEOs who crawl larger sites or need rendering and integrations. If you just want the problems found and fixed once, hiring an audit is often cheaper than buying and learning the tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between Screaming Frog and SE Ranking?
&lt;/h3&gt;

&lt;p&gt;They solve different problems. Screaming Frog is a deep technical crawler that inspects every URL on a site, but it does not track rankings or research keywords. &lt;a href="https://floxolab.com/seo/tools/se-ranking-review" rel="noopener noreferrer"&gt;SE Ranking&lt;/a&gt; is an all-in-one suite for rank tracking, keyword research, and reporting, with a lighter built-in audit. Many people use both: Screaming Frog for the deep technical crawl and an all-in-one tool for tracking and research.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources checked
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.screamingfrog.co.uk/seo-spider/pricing/" rel="noopener noreferrer"&gt;Screaming Frog: SEO Spider pricing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.screamingfrog.co.uk/seo-spider/faq/" rel="noopener noreferrer"&gt;Screaming Frog: SEO Spider FAQ (free version limits)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://floxolab.com/seo/tools/screaming-frog-review" rel="noopener noreferrer"&gt;FloxoLab&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>seo</category>
      <category>webdev</category>
      <category>tools</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Lead Outreach Drafts with n8n, Airtable, Groq, and DeepSeek</title>
      <dc:creator>Stepan Nikonov</dc:creator>
      <pubDate>Sat, 01 Aug 2026 16:34:42 +0000</pubDate>
      <link>https://dev.to/floxolab/lead-outreach-drafts-with-n8n-airtable-groq-and-deepseek-42bb</link>
      <guid>https://dev.to/floxolab/lead-outreach-drafts-with-n8n-airtable-groq-and-deepseek-42bb</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A practical workflow that researches a company website, writes one short outreach draft from real context, and saves it to Airtable for human review before anything is sent.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Each company gets one draft based on its own website. The AI prepares the research and message. A person decides whether it is worth sending.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The bottleneck is not the AI.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is the last 30 seconds of human judgment: read the draft, decide if the lead is worth contacting, edit if needed, then send or skip.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Lead source&lt;/th&gt;
&lt;th&gt;Research step&lt;/th&gt;
&lt;th&gt;Draft step&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Airtable rows with pending websites&lt;/td&gt;
&lt;td&gt;Groq Compound Mini visits the site&lt;/td&gt;
&lt;td&gt;DeepSeek writes a short message&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Screenshots use real public website research from a small test list. Third-party company names and URLs are redacted because those businesses are not FloxoLab clients and are not shown as endorsements. The visible FloxoLab row is included so the research and draft path can be inspected openly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The result in Airtable
&lt;/h2&gt;

&lt;p&gt;The final table is the review queue. Good rows get &lt;code&gt;Status = Done&lt;/code&gt;, plus a draft message and research notes. Broken, parked, or unusable websites are routed to &lt;code&gt;Status = Blocked&lt;/code&gt; or &lt;code&gt;Status = Needs review&lt;/code&gt; instead of forcing a fake personalization.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fohgw16q7p7to62yarh1d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fohgw16q7p7to62yarh1d.png" alt="Airtable table showing lead outreach drafts, notes, done status, blocked status, and one visible FloxoLab test row" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Drafts and notes land in Airtable for review. The workflow prepares messages; a human still decides what happens next.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The full workflow map
&lt;/h2&gt;

&lt;p&gt;The canvas starts with pending Airtable leads, loops through each row, researches the website, checks whether the research is usable, generates a draft only when the site is usable, saves the result, and waits three seconds before the next lead.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnl01vpk8klybqxjn3ebw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnl01vpk8klybqxjn3ebw.png" alt="Full n8n canvas for lead outreach drafts with Airtable, Groq research, DeepSeek draft generation, routing, and save steps" width="799" height="321"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The useful parts are visible: input rows, research, parse checks, draft generation, Airtable update, failed-site routing, and pacing between leads.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Research the company first
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;Research Company&lt;/code&gt; node calls Groq using &lt;code&gt;groq/compound-mini&lt;/code&gt;. Its job is narrow: visit the public website and return factual JSON with a research status field. In this workflow, that field is expected to be &lt;code&gt;done&lt;/code&gt;, &lt;code&gt;blocked&lt;/code&gt;, or &lt;code&gt;unclear&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The research prompt asks for what the company sells, how customers book or inquire, visible calls to action, and a few useful personalization facts. It explicitly tells the model not to invent content for broken or empty websites.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvrmfutsvgoz49dr9y1n8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvrmfutsvgoz49dr9y1n8.png" alt="Research Company node showing Groq Compound Mini website research input and structured JSON output" width="800" height="428"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Groq Compound Mini handles the research step and returns structured context for the next model.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Draft from one real detail
&lt;/h2&gt;

&lt;p&gt;The draft step uses DeepSeek to write a short message from the research. The instruction is intentionally restrained: 3-5 short sentences, under 80 words, one specific detail, no fake enthusiasm, no invented statistics, no generic "businesses like yours" line.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4vqteumw9a9kwnwe6t1j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4vqteumw9a9kwnwe6t1j.png" alt="Generated Draft node showing DeepSeek output for a short lead outreach draft based on company research" width="799" height="428"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The draft is useful because it has context. It is still only a draft until someone reviews it.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Route bad inputs away from drafting
&lt;/h2&gt;

&lt;p&gt;A practical outreach workflow should not pretend every website is usable. If parsing fails, Airtable gets &lt;code&gt;Status = Needs review&lt;/code&gt;. If the website is blocked, broken, parked, or too thin, the row can be updated to &lt;code&gt;Status = Blocked&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8s6urr7tq3wrhl0vz47x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8s6urr7tq3wrhl0vz47x.png" alt="n8n status routing section with Parse OK, Site OK, Parse Failed, Prepare Error Info, and Mark Failed nodes" width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;This route is what keeps the workflow honest: bad input becomes review work, not fake personalization.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the cost is low
&lt;/h2&gt;

&lt;p&gt;This pattern is cheap to test because each row is a small research-and-draft job, not a long agent session. In my Groq account, 16 days of workflow testing showed &lt;code&gt;Current Monthly Usage = $0.26&lt;/code&gt;. That included more than this one workflow and a day where I tested around 100 runs across different models. DeepSeek draft testing was under &lt;code&gt;$0.10&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is not a production guarantee. Real cost depends on website size, model choice, prompt length, retries, and how many leads you run. But for a small outreach test, the bigger cost is usually not model tokens. It is finding a clean lead list and spending the review time well. A small Upwork research task can produce a niche list of real businesses with websites and public contact paths for a low cost.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Airtable:&lt;/strong&gt; stores the review queue, status, notes, and draft message.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Groq Compound Mini:&lt;/strong&gt; researches each public website and returns compact factual JSON.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;DeepSeek:&lt;/strong&gt; writes one short draft from the research instead of a generic template.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Human review:&lt;/strong&gt; protects the workflow from becoming an auto-send spam machine.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Good fits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Niche outreach&lt;/strong&gt; — A freelancer or small team can review 20-50 researched leads instead of sending the same email to a scraped list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shopify wholesale&lt;/strong&gt; — A store can research potential stockists, draft a note from their product fit, and approve only the contacts that look relevant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Workspace follow-up&lt;/strong&gt; — Form or Sheet rows can be enriched before a Gmail draft, Viber follow-up, or owner task is created.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I would improve next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Approval status&lt;/strong&gt; — Add &lt;code&gt;Approved&lt;/code&gt;, &lt;code&gt;Skipped&lt;/code&gt;, and &lt;code&gt;Sent&lt;/code&gt; states in Airtable so sending is a separate decision after review.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual send step&lt;/strong&gt; — After approval, n8n could send the message automatically through Gmail or another tool, then log the sent timestamp and message id.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Follow-up drafts&lt;/strong&gt; — Add a 3-5 day follow-up draft for approved leads that did not reply, with a clear stop condition.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do-not-contact list&lt;/strong&gt; — Check every lead against skipped domains, existing contacts, unsubscribes, and companies that should never be contacted again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Channel variants&lt;/strong&gt; — For some Philippines workflows, the output might be a Gmail draft, LinkedIn note, Viber follow-up, WhatsApp draft, or owner task instead of an email.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Related guide
&lt;/h2&gt;

&lt;p&gt;For the budgeting side of lead work, read &lt;a href="https://floxolab.com/blog/cost-to-automate-lead-follow-up-philippines" rel="noopener noreferrer"&gt;How Much Does It Cost to Automate Lead Follow-Up in the Philippines?&lt;/a&gt;. It breaks down where the money actually goes once outreach turns into ongoing follow-up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;This workflow is useful because it avoids the weakest part of many outreach systems: a generic message sent to every company. It turns public website context into a draft that a person can review.&lt;/p&gt;

&lt;p&gt;It is not a promise of sales results. It is a small, inspectable system for doing better first-contact prep: real research in, short draft out, human approval before send.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://floxolab.com/cases/lead-outreach-drafts-n8n" rel="noopener noreferrer"&gt;FloxoLab&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>ai</category>
      <category>automation</category>
      <category>nocode</category>
    </item>
    <item>
      <title>RAG for Small Businesses: AI That Searches Your Company Knowledge</title>
      <dc:creator>Stepan Nikonov</dc:creator>
      <pubDate>Thu, 30 Jul 2026 23:02:43 +0000</pubDate>
      <link>https://dev.to/floxolab/rag-for-small-businesses-ai-that-searches-your-company-knowledge-137n</link>
      <guid>https://dev.to/floxolab/rag-for-small-businesses-ai-that-searches-your-company-knowledge-137n</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;RAG sounds technical, but the useful idea is simple: let AI search selected company documents before it answers, instead of asking it to guess from a blank chat window.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most small businesses do not have a knowledge problem because they lack information. They have one because the information is scattered. A price note is in one folder, a refund rule inside an old PDF, a support answer buried in email, a delivery process in someone's private notes. The owner, manager, or senior admin becomes the living search engine.&lt;/p&gt;

&lt;p&gt;This is where RAG can be useful. Not as a black box &lt;a href="https://floxolab.com/blog/ai-agents-for-business" rel="noopener noreferrer"&gt;AI agent&lt;/a&gt;. Not as a replacement for good operations. Just as a practical way to help a team search its own knowledge before asking AI to write an answer.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;RAG is not the AI knowing everything.&lt;/strong&gt; It is the AI looking at selected company information first, then answering from that context.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The actual business problem
&lt;/h2&gt;

&lt;p&gt;A small team usually knows the answers. The slow part is finding the right answer at the right moment.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A new employee asks the same onboarding questions every week.&lt;/li&gt;
&lt;li&gt;Sales needs the latest package details before replying to a lead.&lt;/li&gt;
&lt;li&gt;Support has to search old conversations for a policy answer.&lt;/li&gt;
&lt;li&gt;Operations keeps a checklist in one place and exceptions somewhere else.&lt;/li&gt;
&lt;li&gt;The owner becomes the person everyone asks before making a small decision.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A normal keyword search helps only when the person knows the exact word to search for. Real questions are messier. Someone asks "can we still refund this?" while the document says "cancellation window." Someone asks "what do we include in the starter package?" while the pricing file says "basic implementation scope."&lt;/p&gt;

&lt;h2&gt;
  
  
  RAG in plain English
&lt;/h2&gt;

&lt;p&gt;RAG means retrieval-augmented generation. In plain English: the system retrieves relevant information first, gives that information to the AI model, and then asks the model to generate an answer from that context.&lt;/p&gt;

&lt;p&gt;That matters because a normal chatbot can write confidently without knowing your internal rules. A RAG assistant can be told to answer only from selected business documents and to say when the answer is not in the provided knowledge.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsb2k73vacb77xlz2t80j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsb2k73vacb77xlz2t80j.png" alt="Simple workflow diagram showing company documents searched by a knowledge assistant before answering a team chat question" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A simple RAG pattern: selected company documents become searchable knowledge, a team asks a question in chat, and the answer is grounded in retrieved context.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What a vector database does
&lt;/h2&gt;

&lt;p&gt;A vector database helps search by meaning, not only by exact keywords. If someone asks about "refund rules" and the document says "cancellation and return policy," a semantic search can still find the right section. That is the useful part; the business does not need a lecture on embeddings before it can benefit from better internal search.&lt;/p&gt;

&lt;p&gt;One practical detail matters: the embedding step is separate from the answer-writing step. An embedding model turns each approved document chunk into a vector. Qdrant stores and searches those vectors. A language model can then write an answer from the retrieved context. Those jobs should not be blurred together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the tools fit
&lt;/h2&gt;

&lt;p&gt;In a small RAG workflow, each tool should have one clear job.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;n8n coordinates the workflow.&lt;/strong&gt; It receives a question, triggers the search step, sends the retrieved context to the model, logs the result, and returns the answer to the team.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Qdrant stores searchable document chunks.&lt;/strong&gt; It keeps selected company knowledge in a form that can be searched by meaning rather than exact wording.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An embedding model creates the vectors.&lt;/strong&gt; This can be OpenAI’s embedding API or another dedicated embedding model. Its job is to convert the documents and the user’s question into comparable numerical representations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A language model writes the answer.&lt;/strong&gt; Groq or another LLM can be used here, but it should answer from the retrieved context rather than invent a policy that is not in the documents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Slack gives the team a familiar interface.&lt;/strong&gt; Questions and answers can stay inside a private internal channel instead of another dashboard nobody opens.&lt;/p&gt;

&lt;p&gt;The stack itself is not the point. Qdrant, n8n, an embedding model, an answer model, and Slack are useful only when the workflow answers a real operational question faster and more consistently than manual search.&lt;/p&gt;

&lt;h2&gt;
  
  
  Business questions this can help with
&lt;/h2&gt;

&lt;p&gt;A knowledge assistant is most useful when the answer already exists somewhere, but the team wastes time finding it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pain&lt;/th&gt;
&lt;th&gt;Useful first version&lt;/th&gt;
&lt;th&gt;Human check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;New staff asks the same setup questions.&lt;/td&gt;
&lt;td&gt;Search onboarding docs and SOPs from Slack.&lt;/td&gt;
&lt;td&gt;Escalate missing or outdated answers.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sales needs package and pricing details quickly.&lt;/td&gt;
&lt;td&gt;Retrieve the latest service scope, inclusions, and limits.&lt;/td&gt;
&lt;td&gt;Manager approves unusual discounts or exceptions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Support repeats answers from old tickets.&lt;/td&gt;
&lt;td&gt;Search FAQ, policy, and troubleshooting notes.&lt;/td&gt;
&lt;td&gt;Agent reviews before sending to a customer.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operations has checklists across files.&lt;/td&gt;
&lt;td&gt;Find the relevant process step or exception rule.&lt;/td&gt;
&lt;td&gt;Owner confirms edge cases.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is why RAG can be a good fit for small team automation work. Many teams already run on Drive folders, PDFs, spreadsheets, Notion pages, email threads, and chat, and the first win is often not replacing those tools. It is making the right parts searchable.&lt;/p&gt;

&lt;p&gt;For &lt;a href="https://floxolab.com/automation-and-optimization-philippines" rel="noopener noreferrer"&gt;small businesses in the Philippines&lt;/a&gt;, the knowledge layer is often less polished than a formal company wiki. It may be a mix of Google Drive folders, Facebook Messenger threads, WhatsApp or Viber chats, Gmail, shared spreadsheets, and a few documents only one person knows about. A useful AI knowledge assistant should respect that reality: start with the approved files and processes, then connect to the team's actual tools only when the workflow is clear.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this will not fix
&lt;/h2&gt;

&lt;p&gt;RAG does not make bad documents good. If the source material is outdated, contradictory, or missing, the assistant should not pretend otherwise.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It will not decide company policy for you.&lt;/li&gt;
&lt;li&gt;It will not clean years of messy files by itself.&lt;/li&gt;
&lt;li&gt;It should not answer sensitive questions without access control.&lt;/li&gt;
&lt;li&gt;It should not hide uncertainty behind a confident paragraph.&lt;/li&gt;
&lt;li&gt;It still needs testing with real questions from the team.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A good version should be allowed to say: "I cannot find that in the provided documents." That answer is sometimes more valuable than a polished guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start smaller than you think
&lt;/h2&gt;

&lt;p&gt;The first version does not need every file the company has ever created.&lt;/p&gt;

&lt;p&gt;A better starting set might be 10 to 30 useful documents: current pricing, service scope, refund policy, onboarding SOP, support FAQ, delivery checklist, and a few internal notes that people already ask about. That is enough to test whether the assistant retrieves useful context before you spend time connecting more systems.&lt;/p&gt;

&lt;p&gt;The first build should also have a narrow promise: answer internal team questions from selected documents. Not update customer records, not send client replies, not make decisions without review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Questions to map before building
&lt;/h2&gt;

&lt;p&gt;Before turning this into a workflow, map the knowledge first.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which documents are actually current?&lt;/li&gt;
&lt;li&gt;Which questions does the team ask repeatedly?&lt;/li&gt;
&lt;li&gt;Which answers are safe for internal use only?&lt;/li&gt;
&lt;li&gt;Which answers should require manager approval?&lt;/li&gt;
&lt;li&gt;Where should the assistant answer: Slack, email, Notion, or another tool?&lt;/li&gt;
&lt;li&gt;What should happen when the answer is missing or uncertain?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These questions matter more than the model choice. A fast model connected to messy, unreviewed knowledge will still produce messy answers faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;RAG is useful when the business already has answers, but the team loses time finding them. Start with a small, approved knowledge set, test real questions, and make the assistant show uncertainty instead of filling gaps with confident guesses.&lt;/p&gt;

&lt;p&gt;The practical goal is not to make a chatbot sound impressive. It is to help a team find the right internal answer faster, with enough context for a human to trust, check, or improve it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Want to see the workflow version?&lt;/strong&gt; I built an inspectable &lt;a href="https://floxolab.com/cases/slack-knowledge-bot-n8n" rel="noopener noreferrer"&gt;Slack Knowledge Bot with n8n, Qdrant, and Groq&lt;/a&gt; that answers from approved Google Drive docs, shows sources, and marks low-confidence matches.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.n8n.io/advanced-ai/rag-in-n8n/" rel="noopener noreferrer"&gt;n8n RAG documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.vectorstoreqdrant/" rel="noopener noreferrer"&gt;n8n Qdrant Vector Store node documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qdrant.tech/documentation/overview/" rel="noopener noreferrer"&gt;Qdrant documentation overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://platform.openai.com/docs/guides/embeddings" rel="noopener noreferrer"&gt;OpenAI embeddings documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://console.groq.com/docs/models" rel="noopener noreferrer"&gt;Groq models documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://floxolab.com/blog/rag-knowledge-assistant-business" rel="noopener noreferrer"&gt;FloxoLab&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rag</category>
      <category>automation</category>
      <category>llm</category>
    </item>
    <item>
      <title>How to Fix Core Web Vitals: LCP, CLS, and INP in Plain English</title>
      <dc:creator>Stepan Nikonov</dc:creator>
      <pubDate>Sun, 26 Jul 2026 19:33:34 +0000</pubDate>
      <link>https://dev.to/floxolab/how-to-fix-core-web-vitals-lcp-cls-and-inp-in-plain-english-1ob1</link>
      <guid>https://dev.to/floxolab/how-to-fix-core-web-vitals-lcp-cls-and-inp-in-plain-english-1ob1</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;I took this site from a mobile score of 77 to 98, and to 100 on desktop. Here is what each Core Web Vital measures, what drags it down, and the fixes that actually moved the numbers, in plain language you can act on.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Core Web Vitals are three numbers Google uses to measure how a page feels to a real person: how fast the main content loads, whether the layout jumps around while it does, and how quickly the page reacts when you tap or click. They are part of Google's page experience signals, which means they work as a ranking tie-breaker. Between two pages that answer a query equally well, the faster, steadier one has the edge.&lt;/p&gt;

&lt;p&gt;This guide explains each of the three, plus the supporting numbers you will see in the same report, and then shows the real fixes that took this site's mobile score from 77 to 98. Nothing here is theory. It is the same work I run on client sites in an &lt;a href="https://floxolab.com/seo-audit" rel="noopener noreferrer"&gt;SEO audit&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a good result looks like
&lt;/h2&gt;

&lt;p&gt;Here is where this site sits today, straight from Google's PageSpeed Insights: 98 on mobile and 100 on desktop, on the same code. The two tests look different because mobile is run on a throttled, slower simulated device, so it always has less room to work with. Mobile is the harder target, and since mobile traffic dominates most sites, its real-user data usually carries the most ranking weight, so it is the score worth chasing. Google measures mobile and desktop separately, so desktop still counts too.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmk97r5ue0js78zvlb3qh.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmk97r5ue0js78zvlb3qh.webp" alt="PageSpeed Insights mobile report for this site: performance 98, LCP 1.9 seconds, CLS 0, total blocking time 40 milliseconds, all green" width="800" height="727"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Mobile: performance 98, LCP 1.9s, CLS 0, all vitals in the green.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgdqztmqut6gc4zv2aych.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgdqztmqut6gc4zv2aych.webp" alt="PageSpeed Insights desktop report for this site: performance 100, LCP 0.5 seconds, CLS 0, and speed index 0.4 seconds" width="800" height="725"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Desktop: performance 100, LCP 0.5s, CLS 0, speed index 0.4s.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The three Core Web Vitals, one at a time
&lt;/h2&gt;

&lt;p&gt;Everything on the scoreboard comes down to three real-user metrics. A page passes when 75 percent of real visitors fall inside the "good" band for each.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Measures&lt;/th&gt;
&lt;th&gt;Good&lt;/th&gt;
&lt;th&gt;Poor&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LCP (Largest Contentful Paint)&lt;/td&gt;
&lt;td&gt;Loading&lt;/td&gt;
&lt;td&gt;2.5s or less&lt;/td&gt;
&lt;td&gt;Over 4s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLS (Cumulative Layout Shift)&lt;/td&gt;
&lt;td&gt;Visual stability&lt;/td&gt;
&lt;td&gt;0.1 or less&lt;/td&gt;
&lt;td&gt;Over 0.25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;INP (Interaction to Next Paint)&lt;/td&gt;
&lt;td&gt;Responsiveness&lt;/td&gt;
&lt;td&gt;200ms or less&lt;/td&gt;
&lt;td&gt;Over 500ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  LCP: how fast the main thing loads
&lt;/h3&gt;

&lt;p&gt;Largest Contentful Paint is the moment the biggest element in view finishes rendering, usually the hero image or the headline block. It is the reader's gut sense of "has this page loaded yet". What drags it down is nearly always one of four things: a heavy, uncompressed hero image, a slow server or host, render-blocking CSS and JavaScript that must run before anything paints, and web fonts that arrive late.&lt;/p&gt;

&lt;p&gt;The fixes follow the causes. Compress and serve the hero as WebP at the right size, and preload it so the browser fetches it early. Use srcset so a phone pulls a small version and a desktop a larger one, instead of shipping one heavy file to every device. That is a separate lever from preloading: preload fetches the image sooner, srcset makes sure it is the right weight, and on a slow mobile connection the srcset part is often what saves the most. Cut or defer render-blocking scripts, and self-host your fonts instead of pulling them from a third-party domain. On this site LCP is 1.9s on mobile and 0.5s on desktop, and the single biggest lever was the hero image: right format, right size per device, preloaded.&lt;/p&gt;

&lt;h3&gt;
  
  
  CLS: whether the page holds still
&lt;/h3&gt;

&lt;p&gt;Cumulative Layout Shift measures how much the content jumps around as the page loads. You have felt it: you go to tap a link, an image or banner loads above it, and the whole page shoves down. The usual culprits are images and embeds with no width and height set, ad or banner slots injected after load, and fonts that swap and reflow the text.&lt;/p&gt;

&lt;p&gt;This is the most fixable vital, and the cheapest win on the list. Put explicit width and height (or an aspect ratio) on every image and iframe so the browser reserves the space before the file arrives. Give injected elements a fixed slot. Set font-display so a font swap does not reshuffle the layout. This site sits at a CLS of 0, and it got there almost entirely by adding size attributes to images.&lt;/p&gt;

&lt;h3&gt;
  
  
  INP: how fast the page answers you
&lt;/h3&gt;

&lt;p&gt;Interaction to Next Paint replaced First Input Delay in March 2024. It measures the delay between you doing something, a tap, a click, a keypress, and the page actually showing a response, taking close to the worst interaction across the whole visit. A high INP is a page that feels sluggish and unresponsive, and the cause is almost always JavaScript hogging the main thread: heavy frameworks, big third-party scripts, and long tasks that block the browser from reacting. It is also the vital the most sites fail, so it is worth the attention.&lt;/p&gt;

&lt;p&gt;The fix is to give the main thread less to do. Defer or split heavy JavaScript, hold third-party scripts off the critical path until the user needs them, and break long tasks into smaller pieces. The clearest example on this site is analytics: the Google tag does not load until the first interaction or the browser goes idle, so it never blocks that first tap. That one change keeps total blocking time, the lab stand-in for INP, at 40 milliseconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  The other numbers in the report
&lt;/h2&gt;

&lt;p&gt;The same Lighthouse report shows three more metrics. These are lab diagnostics, useful for finding problems, but they are not the vitals Google ranks on.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;First Contentful Paint (FCP).&lt;/strong&gt; When the first text or image appears. An early signal that something is happening.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Total Blocking Time (TBT).&lt;/strong&gt; How long the main thread was blocked during load. It is the lab proxy for INP, so lowering TBT usually lowers INP for real users too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed Index.&lt;/strong&gt; How quickly the page visibly fills in. On this site it shows 3.5s on mobile and lands in the amber band, while every actual vital is green. That is a fair reminder that the overall score is a blend, and a single amber lab number is not worth panicking over when the real-user vitals all pass.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lab data and field data are not the same
&lt;/h2&gt;

&lt;p&gt;There are two ways to measure all of this, and mixing them up causes a lot of confusion. &lt;strong&gt;Lab data&lt;/strong&gt; is a single simulated test, like the Lighthouse run in the screenshots above, on a fixed device and connection. It is repeatable and great for debugging. &lt;strong&gt;Field data&lt;/strong&gt; is what real Chrome users actually experienced over the last 28 days, and it is what Google uses for ranking. You can see your own field data in the Core Web Vitals report inside Search Console.&lt;/p&gt;

&lt;p&gt;The practical order: use the lab test to find and fix problems fast, then watch the field data in Search Console to confirm real users feel the change. A lab score can be perfect while the field still lags, especially on a page that only just got fixed, because the field window is a rolling average. Reading that report is part of the wider habit covered in &lt;a href="https://floxolab.com/seo/guides/how-to-read-your-monthly-seo-report" rel="noopener noreferrer"&gt;how to read your monthly SEO report&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually moved this site from 77 to 98
&lt;/h2&gt;

&lt;p&gt;The jump was not one clever trick. It was a short list of unglamorous fixes, each aimed at a specific metric:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Self-hosted the fonts.&lt;/strong&gt; Pulling fonts from a third-party domain adds a connection and a render delay. Serving them from the same domain, preloaded, cut straight into LCP.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deferred analytics off the critical path.&lt;/strong&gt; The Google tag now loads on first interaction or idle instead of during load, which kept total blocking time, and therefore INP, low.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Responsive images in WebP.&lt;/strong&gt; Every image ships as WebP at several sizes with srcset, so a phone downloads a small file and a desktop a larger one, never the wrong one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Width and height on everything.&lt;/strong&gt; Explicit dimensions on images and figures reserve their space, which is why CLS is 0.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that touched the design or the content. It is exactly the kind of finite, technical work that a small site needs once, not every month, which is the point I make in &lt;a href="https://floxolab.com/seo/guides/technical-seo-fixes-agencies-wont-touch" rel="noopener noreferrer"&gt;the technical SEO fixes agencies won't touch&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Compress the hero image, serve it as WebP, and preload it.&lt;/li&gt;
&lt;li&gt;Set width and height on every image, iframe, and embed.&lt;/li&gt;
&lt;li&gt;Self-host fonts and set font-display so text does not reflow.&lt;/li&gt;
&lt;li&gt;Defer analytics and other third-party scripts off the load path.&lt;/li&gt;
&lt;li&gt;Test in the lab with PageSpeed Insights, then confirm in Search Console field data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What are Core Web Vitals?
&lt;/h3&gt;

&lt;p&gt;Three metrics Google uses to measure real-world page experience: Largest Contentful Paint (LCP) for loading, Cumulative Layout Shift (CLS) for visual stability, and Interaction to Next Paint (INP) for responsiveness. They come from real Chrome users and feed into ranking as a page experience signal.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a good Core Web Vitals score?
&lt;/h3&gt;

&lt;p&gt;A page passes when 75 percent of real users see LCP at 2.5s or less, CLS at 0.1 or less, and INP at 200ms or less. In the Lighthouse lab test the overall score is good from 90 to 100. This site scores 98 on mobile and 100 on desktop.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is my mobile score lower than desktop?
&lt;/h3&gt;

&lt;p&gt;Lighthouse tests mobile on a throttled, slower simulated device and connection, so the same page has less CPU and bandwidth to work with. Mobile is the harder target, and because mobile traffic dominates most sites, its real-user data usually carries the most ranking weight, so it is the score to optimise for. Google measures mobile and desktop separately, so desktop still counts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do Core Web Vitals affect SEO rankings?
&lt;/h3&gt;

&lt;p&gt;Yes, as a tie-breaker rather than a magic lever. Between two pages of similar relevance, the faster and steadier one has an edge. Fixing vitals will not outrank a far more relevant page on its own, but poor vitals can hold back a page that otherwise deserves to rank.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources checked
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://web.dev/articles/vitals" rel="noopener noreferrer"&gt;web.dev: Web Vitals and the Core Web Vitals thresholds&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/search/docs/appearance/page-experience" rel="noopener noreferrer"&gt;Google Search Central: page experience and Core Web Vitals in ranking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pagespeed.web.dev/" rel="noopener noreferrer"&gt;PageSpeed Insights: the lab and field report used above&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://floxolab.com/seo/guides/how-to-fix-core-web-vitals" rel="noopener noreferrer"&gt;FloxoLab&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webperf</category>
      <category>webdev</category>
      <category>seo</category>
      <category>performance</category>
    </item>
    <item>
      <title>Slack Knowledge Bot with n8n, Qdrant, and Groq</title>
      <dc:creator>Stepan Nikonov</dc:creator>
      <pubDate>Fri, 24 Jul 2026 10:49:36 +0000</pubDate>
      <link>https://dev.to/floxolab/slack-knowledge-bot-with-n8n-qdrant-and-groq-253p</link>
      <guid>https://dev.to/floxolab/slack-knowledge-bot-with-n8n-qdrant-and-groq-253p</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A practical workflow that turns approved Google Drive docs into a Slack assistant. It chunks selected files, stores embeddings in Qdrant, retrieves relevant context, asks Groq to answer only from that context, and replies with sources or a low-confidence note.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Many teams already have the answer somewhere: a pricing note, an onboarding doc, a service FAQ, a checklist, or a handoff process. The slow part is finding the right section while work is happening.&lt;/p&gt;

&lt;p&gt;This workflow gives Slack a searchable knowledge layer. A team member asks a question, n8n searches an approved Google Drive folder through Qdrant, and Groq writes an answer from the retrieved context with source files attached.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The useful part is not a confident answer.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is a traceable answer: what documents were searched, which sources were used, and whether the match was weak enough to treat with caution.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Knowledge source&lt;/th&gt;
&lt;th&gt;Search layer&lt;/th&gt;
&lt;th&gt;Team interface&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Approved Google Drive Markdown files&lt;/td&gt;
&lt;td&gt;Qdrant vectors from local embeddings&lt;/td&gt;
&lt;td&gt;Slack mention with sources&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Approximate running cost
&lt;/h2&gt;

&lt;p&gt;For this demo, the software bill can stay very low. Ollama runs the embedding model locally. n8n can run as a free self-hosted community edition. Qdrant can run locally or start on a free cloud tier for testing. Groq is the main paid API part, and a small internal bot like this can often be tested on a small balance when question volume is modest.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ollama embeddings:&lt;/strong&gt; no API bill when the embedding model runs on your own machine or server. The cost is local compute.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;n8n:&lt;/strong&gt; free if self-hosted, with hosting and maintenance still owned by someone. n8n Cloud is paid if you want managed hosting.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Qdrant:&lt;/strong&gt; local/self-hosted can be free to run as software. Qdrant Cloud also has a free tier for testing and prototypes, with paid usage when the project grows.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Groq:&lt;/strong&gt; the answer generation step is usage-based. For a low-volume demo, a small balance such as USD 5 can be enough to test the pattern for a while, but real spend depends on model choice, prompt size, and usage volume.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Low software cost to start. Real cost is hosting, API calls, and whoever maintains it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Groq:&lt;/strong&gt; the answer generation step is usage-based. For a low-volume demo, a small balance such as USD 5 can be enough to test the pattern for a while, but real spend depends on model choice, prompt size, and usage volume.&lt;/p&gt;

&lt;p&gt;Low software cost to start. Real cost is hosting, API calls, and whoever maintains it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The full workflow map
&lt;/h2&gt;

&lt;p&gt;The canvas has two lanes. The top lane refreshes the knowledge base: list files, filter approved text files, chunk them, embed them, delete old chunks for the same source, and upsert the fresh points into Qdrant. The bottom lane answers questions from Slack.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frgypqzbw8itw010qh32q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frgypqzbw8itw010qh32q.png" alt="n8n workflow canvas for a Slack knowledge bot with Google Drive ingestion and Slack answer paths" width="800" height="368"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Two visible paths: document ingestion on top, Slack question answering below.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Start with approved docs
&lt;/h2&gt;

&lt;p&gt;The workflow starts from a controlled Google Drive folder. In this demo, the folder contains Markdown files for FloxoLab pricing, services, FAQ, workflow examples, onboarding, and intake process notes.&lt;/p&gt;

&lt;p&gt;That matters because a useful knowledge bot should not begin by crawling a messy shared drive. The first version should use current, approved documents that are safe for internal answers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F802xiv2ro9lm1jidp4k7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F802xiv2ro9lm1jidp4k7.png" alt="Google Drive demo folder containing selected Markdown knowledge files" width="799" height="322"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The workflow filters for Markdown files, so the first knowledge base stays simple and reviewable.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Chunk the files with metadata
&lt;/h2&gt;

&lt;p&gt;Each file is downloaded and passed through a code node that splits text by headings, keeps chunks under a practical word limit, and adds metadata such as source, file index, chunk index, word count, and last modified time.&lt;/p&gt;

&lt;p&gt;Metadata is not decoration. It is what lets the answer show where the context came from, and it gives the workflow a clean way to replace old chunks from the same source during the next ingestion run.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff41je3arqox47vf9zu5b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff41je3arqox47vf9zu5b.png" alt="n8n Smart Chunk and Metadata output showing chunk text source chunk index and word count" width="800" height="866"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Each chunk carries source and index data, not just text.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Store searchable chunks in Qdrant
&lt;/h2&gt;

&lt;p&gt;The workflow uses Ollama with &lt;code&gt;nomic-embed-text&lt;/code&gt; for embeddings, then prepares a Qdrant point with a deterministic id, vector, and payload. Before upserting new points, it deletes old chunks for the same source so stale content does not sit beside fresh content.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faruravnt52hov99czkle.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faruravnt52hov99czkle.png" alt="Qdrant collection showing stored knowledge chunks with text source chunk index and vector length" width="800" height="626"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Qdrant stores the vector plus the payload fields the workflow needs later: text, source, chunk index, timestamps, and word count.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Ask from Slack
&lt;/h2&gt;

&lt;p&gt;The question flow starts when someone mentions the bot in Slack. n8n removes the mention text, embeds the clean question, searches Qdrant for the top matching chunks, and builds a compact context block for Groq.&lt;/p&gt;

&lt;p&gt;The answer prompt is deliberately strict: answer only from the provided context, do not invent pricing, services, guarantees, timelines, or policies, and keep the message concise enough for Slack.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyb57u4a0ebel784zzd4f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyb57u4a0ebel784zzd4f.png" alt="Slack question and bot answer showing sources from approved knowledge files" width="800" height="209"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The user asks inside Slack. The bot answers from the retrieved context and appends source files.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Make uncertainty visible
&lt;/h2&gt;

&lt;p&gt;The workflow also marks weak retrieval. If the top Qdrant score is below the threshold, the answer includes a low-confidence note. That is a practical guardrail because the user can see the answer should be checked before it becomes a decision.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftxawqwdgqf0d9wl0hzck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftxawqwdgqf0d9wl0hzck.png" alt="Slack bot low confidence response when the answer is not found in the approved documents" width="800" height="177"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A missing answer should stay visible. Here the bot says it does not know and marks the match as low confidence.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would improve next
&lt;/h2&gt;

&lt;p&gt;The demo proves the path, but a production version should make ownership clearer.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Better source links.&lt;/strong&gt; File names are useful, but links back to the exact Google Drive document would make review faster.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Threaded Slack replies.&lt;/strong&gt; Posting answers in the original thread would keep channels cleaner.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Low-confidence review.&lt;/strong&gt; Weak matches could be logged to a sheet or sent to a private review channel.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ingestion logs.&lt;/strong&gt; A clear record of files, chunk counts, and failures would make handoff easier.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;A Slack knowledge bot is useful when the answer already exists, but the team wastes time finding it. The important choices are not only model and database. They are the approved knowledge set, the retrieval threshold, the source display, and the fallback behavior.&lt;/p&gt;

&lt;p&gt;This workflow keeps those parts visible: selected docs in, searchable chunks stored, Slack question asked, context retrieved, answer written from that context, sources shown, uncertainty surfaced.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related guide
&lt;/h2&gt;

&lt;p&gt;For the plain-English explanation behind this pattern, read &lt;a href="https://floxolab.com/blog/rag-knowledge-assistant-business" rel="noopener noreferrer"&gt;RAG for Small Businesses: AI That Searches Your Company Knowledge&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://floxolab.com/cases/slack-knowledge-bot-n8n" rel="noopener noreferrer"&gt;FloxoLab&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>ai</category>
      <category>rag</category>
      <category>automation</category>
    </item>
    <item>
      <title>n8n Webhook Not Working? Test URL, Production URL, and 404 Fixes</title>
      <dc:creator>Stepan Nikonov</dc:creator>
      <pubDate>Tue, 21 Jul 2026 07:25:33 +0000</pubDate>
      <link>https://dev.to/floxolab/n8n-webhook-not-working-test-url-production-url-and-404-fixes-2c17</link>
      <guid>https://dev.to/floxolab/n8n-webhook-not-working-test-url-production-url-and-404-fixes-2c17</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;If an n8n webhook receives nothing or returns 404, check the URL mode and registration state before changing the workflow. This guide follows the shortest useful diagnostic order.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An n8n webhook normally fails before the first workflow node runs. A 404 usually means the request did not match a registered webhook route. The common causes are a test URL with no active listener, a production URL for an unpublished workflow, the wrong HTTP method, a changed path, or an incorrect public URL on a self-hosted instance.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Fastest check:&lt;/strong&gt; open the Webhook node, copy the URL again, confirm whether it is Test or Production, and send the same HTTP method configured in the node. Do this before debugging credentials, expressions, or downstream nodes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Start with the symptom
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Most likely check&lt;/th&gt;
&lt;th&gt;First action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Test URL returns 404&lt;/td&gt;
&lt;td&gt;No current test listener&lt;/td&gt;
&lt;td&gt;Select &lt;strong&gt;Listen for test event&lt;/strong&gt;, then resend within 120 seconds.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Production URL returns 404&lt;/td&gt;
&lt;td&gt;Workflow is not published&lt;/td&gt;
&lt;td&gt;Publish the workflow and copy the Production URL again.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;URL works in a browser but not from the app&lt;/td&gt;
&lt;td&gt;GET versus POST mismatch&lt;/td&gt;
&lt;td&gt;Match the sender's method to the Webhook node.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;URL has the wrong domain, port, or protocol&lt;/td&gt;
&lt;td&gt;Self-hosted proxy configuration&lt;/td&gt;
&lt;td&gt;Check &lt;code&gt;WEBHOOK_URL&lt;/code&gt;, proxy hops, and forwarded headers.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Webhook runs but no data appears on the canvas&lt;/td&gt;
&lt;td&gt;Production execution&lt;/td&gt;
&lt;td&gt;Open the workflow's &lt;strong&gt;Executions&lt;/strong&gt; tab.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Test URL versus Production URL
&lt;/h2&gt;

&lt;p&gt;Every Webhook node exposes two URLs at the top of its panel. The &lt;a href="https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.webhook/" rel="noopener noreferrer"&gt;official Webhook node documentation&lt;/a&gt; treats them as two different operating modes, not interchangeable copies of the same endpoint.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use the Test URL while building
&lt;/h3&gt;

&lt;p&gt;Select &lt;strong&gt;Test URL&lt;/strong&gt;, then select &lt;strong&gt;Listen for test event&lt;/strong&gt; before the external system sends its request. n8n registers the test webhook for 120 seconds and displays the incoming data in the editor. If the timer expires, register it again and resend the event.&lt;/p&gt;

&lt;p&gt;Opening the workflow is not enough by itself. A saved test URL can return 404 later because test registration is temporary. This is useful during development because it lets one test payload appear directly on the canvas, but it is the wrong URL for a service that must call n8n at any time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use the Production URL after publishing
&lt;/h3&gt;

&lt;p&gt;Select &lt;strong&gt;Production URL&lt;/strong&gt;, copy it into the sending service, and &lt;strong&gt;Publish&lt;/strong&gt; the workflow. Current n8n documentation uses Publish terminology. Publishing registers the production webhook and makes the published workflow version available to external requests until the workflow is unpublished.&lt;/p&gt;

&lt;p&gt;Production payloads do not appear live on the editor canvas. Open &lt;strong&gt;Executions&lt;/strong&gt; to confirm whether the request created a production execution and to inspect its input. No data on the canvas does not mean the webhook failed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The n8n webhook 404 checklist
&lt;/h2&gt;

&lt;p&gt;Run these checks in order. Stop as soon as the request reaches n8n.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Copy the URL from the node again.&lt;/strong&gt; Do not rely on a URL saved in an old note, form, app, or API client.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confirm Test or Production.&lt;/strong&gt; Test requires a temporary listener. Production requires a published workflow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confirm the HTTP method.&lt;/strong&gt; A POST request does not match a webhook registered only for GET.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compare the path exactly.&lt;/strong&gt; Check spelling, hyphens, route parameters, and any path changed after the sender was configured.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check for a path-and-method conflict.&lt;/strong&gt; n8n permits only one registered webhook for each path and method combination.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inspect the public base URL.&lt;/strong&gt; Do this only for self-hosted n8n behind a reverse proxy or when the node displays an internal hostname, HTTP instead of HTTPS, or the wrong port.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Check the HTTP method before the payload
&lt;/h2&gt;

&lt;p&gt;The Webhook node supports GET, POST, PUT, PATCH, DELETE, and HEAD. By default it accepts one method. The sender must use that exact method. Pasting a webhook URL into a browser sends GET, so a successful browser test does not prove that a service's POST request will match, and the reverse is also true.&lt;/p&gt;

&lt;p&gt;Current n8n versions can allow multiple HTTP methods from the node's Settings. Use that only when the endpoint genuinely needs more than one method. A tighter single-method webhook is easier to reason about and reduces accidental calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the webhook path
&lt;/h2&gt;

&lt;p&gt;n8n generates a random path by default, and you can replace it with a stable custom path. Once an external app stores the URL, changing the Path field changes the route it must call. Copy the complete URL after every path change instead of editing only the last segment by hand.&lt;/p&gt;

&lt;p&gt;n8n also requires each registered path and HTTP method combination to be unique. If another published workflow already owns the same combination, unpublish the conflicting workflow or choose a different path or method. The &lt;a href="https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.webhook/common-issues/" rel="noopener noreferrer"&gt;Webhook common-issues page&lt;/a&gt; documents both method matching and route conflicts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Send a minimal request
&lt;/h2&gt;

&lt;p&gt;Remove the external app from the diagnosis. Copy the exact URL from the Webhook node and test it with a safe placeholder payload. The example below uses POST. Change the method if your node uses something else.&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;--request&lt;/span&gt; POST &lt;span class="s1"&gt;'https://n8n.example.com/webhook/example-path'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{"event":"webhook-test","id":"demo-001"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the example URL with the full Test or Production URL copied from your node. For a Test URL, select &lt;strong&gt;Listen for test event&lt;/strong&gt; immediately before running the command. For a Production URL, publish first. If this request works, the remaining problem is in the external app's stored URL, method, headers, authentication, or network access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reverse proxy and public URL problems
&lt;/h2&gt;

&lt;p&gt;Skip this section on n8n Cloud. It matters when a self-hosted instance runs internally on one address but a reverse proxy exposes a different public HTTPS address.&lt;/p&gt;

&lt;p&gt;n8n normally builds webhook URLs from &lt;code&gt;N8N_PROTOCOL&lt;/code&gt;, &lt;code&gt;N8N_HOST&lt;/code&gt;, and &lt;code&gt;N8N_PORT&lt;/code&gt;. Behind a proxy, that can produce an internal port or the wrong scheme. The &lt;a href="https://docs.n8n.io/hosting/configuration/configuration-examples/webhook-url/" rel="noopener noreferrer"&gt;official reverse-proxy configuration&lt;/a&gt; says to set the public URL explicitly with &lt;code&gt;WEBHOOK_URL&lt;/code&gt;, set &lt;code&gt;N8N_PROXY_HOPS&lt;/code&gt; to the number of reverse proxies, and pass the forwarded host, protocol, and client information from the last proxy.&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_URL&lt;/span&gt;=&lt;span class="n"&gt;https&lt;/span&gt;://&lt;span class="n"&gt;n8n&lt;/span&gt;.&lt;span class="n"&gt;example&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;/
&lt;span class="n"&gt;N8N_PROXY_HOPS&lt;/span&gt;=&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Do not copy this blindly.&lt;/strong&gt; Use your real public HTTPS base URL and the actual number of proxies in the request path. Restart or redeploy n8n after changing environment variables, then copy the regenerated webhook URL from the node.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What to log before handing the webhook into production
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The final Production URL location and which external system owns it. Do not paste secret query parameters into a public document.&lt;/li&gt;
&lt;li&gt;The expected HTTP method, content type, authentication method, and a sanitized example payload.&lt;/li&gt;
&lt;li&gt;The workflow owner and where production executions are reviewed.&lt;/li&gt;
&lt;li&gt;The expected success response and what the sender does when it receives a non-2xx response.&lt;/li&gt;
&lt;li&gt;The alert channel and recovery owner for failures after the webhook starts the workflow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once the route works, broader failures belong in the &lt;a href="https://floxolab.com/blog/n8n-debug-failed-workflows" rel="noopener noreferrer"&gt;n8n workflow debugging guide&lt;/a&gt;. Production ownership and notifications belong in the &lt;a href="https://floxolab.com/blog/n8n-error-workflow-telegram-alerts" rel="noopener noreferrer"&gt;n8n error workflow alerts guide&lt;/a&gt;. Keeping those jobs separate makes the webhook checklist short enough to use during an incident.&lt;/p&gt;

&lt;h2&gt;
  
  
  The production handoff
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Test with the Test URL and one sanitized payload.&lt;/li&gt;
&lt;li&gt;Switch the node to Production URL and copy the full value.&lt;/li&gt;
&lt;li&gt;Publish the workflow.&lt;/li&gt;
&lt;li&gt;Update the external service with the Production URL and matching method.&lt;/li&gt;
&lt;li&gt;Trigger one real but safe event.&lt;/li&gt;
&lt;li&gt;Confirm the run in Executions and record who owns failures.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;n8n autosaves edits, but saved draft changes are not the same as the published version used by production executions. The &lt;a href="https://docs.n8n.io/workflows/publish/" rel="noopener noreferrer"&gt;save and publish documentation&lt;/a&gt; explains that production runs continue to use the current published version until you publish newer changes.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://floxolab.com/blog/n8n-webhook-not-working" rel="noopener noreferrer"&gt;FloxoLab&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>automation</category>
      <category>debugging</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
