From Send to Conversation: Handling Replies & Follow‑Ups in n8n


Table of Contents

Introduction

Open rates are noisy; conversations drive business. If your outbound process can send but not listen, you’ll miss genuine buying signals, annoy busy recipients with off‑timing follow‑ups, and lose track of which threads are worth your time. This guide shows how to transform a one‑way sender into a reply‑aware outreach system using n8n, Gmail/IMAP, and Google Sheets—with pragmatic classification (rules + LLM), routing (Slack/CRM), and follow‑up sequencing that respects time zones and human context.This article is part of our n8n series. The previous post covers sending personalized emails from Sheets via OpenAI and Gmail. Here we build the other half of the loop: detect replies, understand intent, and take the right next step. For context, see our previous guide or contact our team.

1) Why Replies Matter More Than Open Rates

Opens are affected by privacy features and image blocking; replies are concrete signals of intent. Optimizing for reply quality, not clickbait opens, leads to better relationships and accurate forecasting.

1. Timely context

If someone writes “back next Tuesday,” your system should know to pause and snooze until then.

2. Respectful outreach

Unsubscribe requests are captured and honored automatically.

3. Resource allocation

Teams react quickly to “interested” or “referral” replies, not just opens.

4. Measurability

Reply labels (interested, not now, OOO) inform campaign quality and future targeting.

Metrics to track: positive‑reply rate, time‑to‑first‑reply, follow‑up conversion, suppression growth.

2) Architecture Overview

  1. Outbound sends occur via your existing n8n flow (Sheets → LLM → Gmail). Each message carries a unique token and yields a threadId/messageId stored in Sheets.
  2. A Gmail/IMAP watcher polls the reply‑to inbox and pulls new messages thread‑by‑thread.
  3. A Code node strips quoted history and signatures to isolate the fresh text.
  4. A rules stage handles deterministic cases (unsubscribe, bounces, out‑of‑office). Anything else goes to an LLM classifier that returns { label, confidence, summary }.
  5. A router fans out to the right actions: Slack alert, CRM task, snooze job, suppression list update.
  6. A scheduler checks daily for contacts without replies after a window and sends F1/F2 in the same thread, then stops.
  7. Write‑backs keep Sheets updated for idempotency, analytics, and compliance.

Design principle: keep ingestion, understanding, decision, and action as separate stages. It’s easier to debug and evolve.

3) IMAP/Gmail Watcher Setup (threadId, messageId, tokens)

Prerequisites

  • Dedicated reply‑to inbox (e.g., hello@yourdomain.com) with SPF/DKIM/DMARC configured.
  • n8n credentials for Gmail/IMAP with least‑privilege scopes.

Tag outbound emails

  • Append a short token to the subject or signature, e.g., [#LS‑{{rowId}}].
  • Store rowId, email, sent_at, messageId, threadId, campaign in your Contacts sheet after sending.

Why tokens and thread IDs?

threadId keeps messages in the same Gmail conversation. The token survives forwards or clients that break threading and acts as a human‑readable cross‑reference.

n8n nodes

Thread‑safe matching

  1. First try threadId to match to the originating contact row.
  2. Fallback to token lookup via a regex on subject/body if threadId is missing/changed.

4) Stripping Quoted Text & Signatures (Code node)

Most replies include quoted history, legal footers, or mobile signatures. You want the fresh paragraph(s).

  • Stop at common quote delimiters: lines starting with >, patterns like On Tue, … wrote:, From:, Sent:.
  • For HTML replies, convert to text first (preserve line breaks), then apply the split.

Trim after removing signature blocks like dashes or Sent from my iPhone.

Code Mode
// Inputs: $json.html or $json.text from IMAP/Gmail node
function htmlToText(html) {
 return html
  .replace(/(\s*)/gi, ‘\n’)
  .replace(/<\/(p|div|li|tr)>/gi, ‘\n’)
   .replace(//gi, ”)
  .replace(//gi, ”)
  .replace(/<[^>]+>/g, ”)
  .replace(/\n{2,}/g, ‘\n’)
  .trim();
}

const raw = $json.html ? htmlToText($json.html) : ($json.text || ”);
const QUOTE_SPLIT = /\nOn .*?wrote:|\nFrom:|\nSent:|^>+/m; // conservative
let fresh = raw.split(QUOTE_SPLIT)[0].trim();

// Remove common mobile signatures
fresh = fresh.replace(/Sent from my (iPhone|Android).*/i, ”).trim();

return [{ json: { freshReply: fresh } }];

Multi‑language note: add localized variants of On <day> wrote for your markets; keep patterns conservative.

5) Rules vs. LLM Classification (unsubscribe, OOO, interested, not now)

Use rules first for safety and cost control; fall back to an LLM for nuance.

Rules stage (deterministic)

Code Mode
const t = ($json.freshReply || ”).toLowerCase();
const isUnsub = /\b(unsubscribe|remove|opt\s*out|do not contact|stop)\b/i.test(t);
const isOOO = /\b(out of office|vacation|away until|auto-?reply|ooo)\b/i.test(t);
const isBounce= /(mail delivery failed|undeliverable|550 5\.1\.1|552 5\.)/i.test(t);

LLM stage (semantic)

Labels: interested, scheduling, referral, not_now, question, ooo, unsubscribe, bounce, other

Code Mode
System:
You are a concise email reply classifier for LogicalStreet Technology Pvt. Ltd. Be precise and neutral.

User:
Classify the following email reply and return strict JSON only.
Labels: interested, scheduling, referral, not_now, question, ooo, unsubscribe, bounce, other.
Rules: If any unsubscribe intent appears, label as unsubscribe. If DSN/bounce indicators appear, label as bounce. If out-of-office, label as ooo.
JSON Schema: {“label”:”…”,”confidence”:0-1,”summary”:”…”}

Text:
{{freshReply}}

Confidence thresholds: if confidence < 0.5, downgrade to other and route for human review.

6) Routing Actions: Slack, CRM Task, Snooze Logic

Once you have {label, confidence, summary}, route decisively.

Interested / Scheduling

  • Slack: notify a channel with contact details, summary, and a link to the Gmail thread.
  • CRM: create/associate a task or deal with the owner from your assignment rules.
  • Draft Reply (optional): pre‑fill a templated response (human review recommended).

OOO

Parse a return date if present and schedule a snooze to resume in that week. If no date, default to 7–10 business days.

Unsubscribe

Add to a suppression list; mark contact status=suppressed globally. Avoid future sends across campaigns.

Bounce

Mark status=invalid and consider verifying the domain or alternative contacts.

Not now / Question / Other

Create softer tasks with longer due dates or route to a support/solutions mailbox.

Slack template (example)

Slack template (example)
{
  “text”: “Reply classified as *{{label}}* ({{confidence}}). {{email}} — {{summary}}”,
  “attachments”: [
   { “text”: “Thread: {{threadLink}}\nOwner: {{owner}}\nCampaign: {{campaign}}” }
 ]
}

7) Follow‑Up Sequencing (F1/F2), Timing & Stop Conditions

Policy: cap at two follow‑ups (F1, F2) in the same thread.

Timing

  • Wait 3–5 business days between original → F1 and 5–7 between F1 → F2.
  • Send during local business hours (recipient’s time zone).

Stop conditions

  • Any reply (of any label) since the last send.
  • Unsubscribe, bounce, or suppression flag.
  • Owner marks contact as handled.

Implementation in n8n

  1. A Cron job runs daily. It queries the Contacts sheet for rows with status=sent, no reply yet, and last_sent_at older than the window.
  2. Code builds the follow‑up body; keep it shorter and add context.
  3. Gmail sends with the saved threadId to maintain continuity.

Follow‑ups should be helpful, not pushy; one line of new value beats a second pitch.

8) Data Model in Google Sheets (contacts, replies, suppression)

Contacts (source of truth)
rowId, email, firstName, company, role, campaign,
threadId, messageId, token, sent_at, last_sent_at, sends,v status, owner, locale, time_zone
  • status values: new | sent | replied | suppressed | invalid | handled
  • sends: integer counter for follow‑ups
Replies log (append‑only)
rowId, email, reply_at, freshReply,
label, confidence, summary, messageId, threadId

Suppression

Replies log (append‑only)
email, reason, added_at, source

Why Sheets? It’s simple, collaborative, and versionable. For scale or privacy, move to a database (Postgres/BigQuery) but keep equivalent fields.

9) Production Hardening & Observability

Idempotency

  • Use messageId and rowId to avoid duplicate processing.
  • Guard sends with status checks and increment sends safely.

Rate limits & pacing

  • Separate dev vs prod credentials.
  • Pace IMAP/Gmail polling (1–5 minutes) and aggregate Slack alerts.

Logging & dashboards

  • Append to a run log: run_id, started_at, items_processed, interested, ooo, unsubscribes, bounces, failures.
  • Visualize in Looker Studio or your BI tool (reply rate, positive rate, time‑to‑reply).

Monitoring & alerts

  • Send a summary to Slack after each run (counts by label, errors).
  • Page humans only on thresholds (e.g., sudden spike in bounces).

Versioning

  • Track prompt_version for the LLM classifier; roll back if label distribution drifts.

10) Security, Privacy & Compliance

  • Least privilege: minimize scopes for Gmail/IMAP and CRM tokens; rotate regularly.
  • PII hygiene: store only necessary fields; redact message bodies in logs when not essential.
  • Consent: ensure lawful basis for contacting recipients; honor unsubscribe immediately.
  • Transparency: keep a short opt‑out line; avoid deceptive subjects.
  • Access control: restrict who can view/edit the workflow and source data.

11) Example n8n Node Map + Code Snippets

  1. Trigger: IMAP Email (polling)
  2. IF: new mail since last run
  3. Code: HTML→text, quote/signature stripping → freshReply
  4. IF / Switch: rules stage (unsubscribe, bounce, OOO)
  5. Basic LLM Chain: semantic classification → {label, confidence, summary}
  6. Code (Router): build actions and messages
  7. Slack: post structured alert for interested/scheduling/question
  8. CRM: create task/deal; assign owner
  9. Sheets Update: write label/status to Contacts + append to Replies log
  10. Wait/Scheduler: snoozes + follow‑up cadence

Gmail (Send): F1/F2 in original thread

Helper snippets
function parseMaybeJson(s){
   if(!s) return {};
    if(typeof s !== ‘string’) return s;
    const cleaned = s.replace(/“`json|“`/g,”).trim();
    try { return JSON.parse(cleaned); } catch { return {}; }
}

const last = new Date($json.last_sent_at);
const now = new Date();
const days = (now – last) / 86400000;
const eligible = $json.status === ‘sent’ && !$json.replied && days >= 4 && $json.sends < 3;
return [{ json: { eligible } }];

12) Troubleshooting: Common Pitfalls & Fixes

Thread mismatch

Symptom: reply not linked to the original contact. Fix: fallback from threadId to token search; store both.

Over‑eager quote stripping

Symptom: fresh text cut off aggressively. Fix: simplify regex; log raw and stripped pairs for sampling.

LLM returns non‑JSON

Symptom: parse errors. Fix: strip code fences; add try/catch; lower temperature.

Unsubscribe missed

Symptom: user asks to be removed again. Fix: expand regex synonyms; prioritize rules; keep a global suppression list.

Snooze never fires

Symptom: OOO replies not resumed. Fix: store a resume_after timestamp and run a daily scheduler to re‑queue.

Duplicate follow‑ups

Symptom: F1/F2 fired twice. Fix: increment and check sends atomically; write back before sending.

FAQs

Use OAuth2 with least-privilege access and restrict spreadsheet permissions to the sending account. Limit API keys to the workflow’s scope.

Start with a balanced chat model and keep prompts concise. Use a low temperature (0.2–0.3) for consistency, higher (0.6–0.8) for variety.

Write back a status=sent or messageId to Sheets and filter rows in the next run. Consider Merge-by-Key on email when joining streams.

We recommend two (F1/F2) max. It’s effective without being intrusive—especially when follow‑ups add new value.

Add localized patterns to the rules and pass a locale hint to the classifier so it can interpret time expressions and tone accurately.
THE FINAL WORD
Reply awareness turns a sending engine into a conversation system. With n8n, the pieces are modular: IMAP/Gmail for ingestion, a Code node to isolate fresh text, a layered classifier for intent, and clean routing into Slack and your CRM. Respectful follow‑up cadence and precise write‑backs keep the loop tight. The result: fewer missed opportunities, less noise, and a workflow your team trusts. Looking for help implementing or auditing a similar flow? LogicalStreet Technology Pvt. Ltd. can tailor the schema, prompts, and routing to your stack and compliance needs. Explore our workflow automation services, review the sending guide from this series, or contact our team.

Let’s Get Started

Appendix: Implementation Checklist

Credentials & Mailbox

  • Dedicated reply‑to inbox with SPF/DKIM/DMARC
  • n8n Gmail/IMAP credentials with least‑privilege scopes

Outbound Hygiene

  • Subject or footer token (e.g., [#LS‑{{rowId}}])
  • Store threadId & messageId after each send
  • Keep status, sends, and last_sent_at updated

Ingestion

  • IMAP watcher polls every 1–5 minutes
  • Capture subject, from, body, threadId, messageId, received_at

Parsing & Classification

  • HTML→text conversion + quote/signature stripping
  • Rules for unsubscribe, bounce, OOO
  • LLM classifier with JSON contract + error repair

Routing

  • Slack alerts to the right channel with context
  • CRM task/deal creation with owner rules
  • Snooze jobs for OOO with resume_after
  • Suppression list write‑backs for unsubscribes

Follow‑Ups

  • Cron checks eligibility for F1/F2 (3–5 and 5–7 business days)
  • Send in original thread using threadId
  • Stop on any reply/suppression/bounce

Observability

  • Run log with counts by label & failures
  • Daily/weekly dashboards (reply, positive reply, time‑to‑reply)

Security & Compliance

  • Access controls on workflow and sheets
  • Data retention policy for message bodies
  • Immediate unsubscribe honoring across campaigns
Mayurkumar Patel

Mayurkumar is a Software Architect and an Engineer whose passion is to challenge real-world problems using technology. He has lent his expertise to great organizations like Tata Consultancy Services, Balance IT, Zipcar, Gobble, Fitard, and more. He excels in Agile Project Management, Designing & Architecture of Software, and leads his teams to transform ideas into real products. He is also an ace at setting up the Modern Cloud Infrastructure and maintaining the same.


Leave a Reply

Your email address will not be published. Required fields are marked *