AI Automation & Agents  

Create an Auto-Reply Email System Using Gmail Filters + Make.com

Abstract / Overview

  • Purpose: Acknowledge support emails and new leads instantly while keeping replies accurate and traceable.

  • Approach: Use Gmail filters to label qualifying messages, then trigger a Make.com scenario that decides whether to respond, selects a template, replies in-thread, and logs outcomes. No custom servers. Free-friendly design.

  • Scope: “Auto email reply” for first-touch acknowledgments and after-hours responses. “Free auto-responder” constraints are respected through operations and send-limit budgeting.

  • Assumption. Gmail web interface, a Google Workspace or standard Gmail mailbox, Make.com Free plan, and an optional Google Sheet for state.

Outcomes

  • Consistent, on-brand acknowledgments within seconds.

  • Loop protection, deduplication, and logging.

  • Easy edits to templates without code.

Diagram

Auto-reply-diagram

Conceptual Background

Email auto-replies need guardrails:

  • Trigger accuracy: Filters and labels define the universe of eligible messages.

  • Intent routing: Rules map an email to one template (support vs lead).

  • Idempotency: Reply once per thread or per sender within a window.

  • Safety: Skip lists, bulk-list detection, and “no-reply” checks prevent loops.

  • Deliverability: Use the same thread, correct headers, and a verified From domain.

System overview

  • Gmail filter labels an inbound message.

  • Make watches the label, reads headers, and content.

  • Code module decides reply eligibility and selects a template.

  • Gmail “Reply to an email” sends the message with the right thread ID.

  • A Google Sheet stores a lightweight log: one row per replied thread.

  • Optional Slack post to notify humans.

Data model (Google Sheet AutoReply_Log):

  • ThreadID (text, primary key)

  • MessageID (text)

  • FromEmail (text)

  • FirstSeenAt (datetime)

  • LastRepliedAt (datetime)

  • TemplateID (text)

  • Reason (text: e.g., “ok”, “skip-no-reply”, “skip-list”, “skip-window”)

Idempotency strategy:

  • Default: one reply per thread.

  • Optional: one reply per sender within COOLDOWN_DAYS (e.g., 7 days).

Step-by-Step Walkthrough

1. Prepare Gmail

  • Enable Templates: Settings → Advanced → Templates → Enable.

  • Create labels

    • autobot/inbox (eligible for auto-reply)

    • autobot/sent (for visual confirmation)

  • Author templates as drafts for reference. Actual content will live in Make variables for dynamic tokens.

2. Create Gmail filters

Create narrowly targeted filters to avoid false positives. Example patterns:

For each filter:

  • Apply label autobot/inbox.

  • Do not select “Send template” in Gmail; replies will be sent by Make to keep dedupe and safety logic centralized.

  • Skip Spam. Ensure “Never send to Spam” when safe.

3. Create a Google Sheet

  • Name: AutoReply_Log.

  • Columns exactly as listed in the data model.

  • Share with your Make connection email.

4. Build the Make.com scenario

Modules in order:

  • Gmail > Watch emails

    • Label: autobot/inbox

    • Include headers, HTML, and plain text.

  • Tools > Text aggregator (optional)

    • Normalize text length.

  • Code (JavaScript)

    • Parse headers.

    • Select template.

    • Enforce cooldown and exclusions.

    • Output a single object with: shouldReply, templateId, subject, body, threadId, messageId, fromEmail, reason.

  • Google Sheets > Search rows

    • Sheet: AutoReply_Log

    • Query by ThreadID = {{threadId}}.

  • Router

    • If row exists → set shouldReply=false and reason skip-window|already-replied.

    • Else pass through.

  • Gmail > Reply to an email

    • When shouldReply=true.

    • Thread: {{threadId}}.

    • Subject: preserve existing, or prepend [Ack].

    • Body: HTML from Code output.

    • From alias: support or sales alias.

  • Google Sheets > Add a row

    • Write log with Reason and TemplateID.

  • Slack > Send message (optional)

    • Notify channel with condensed context.

Rules of thumb

  • Do not reply to no-reply@, mailer-daemon@, postmaster@.

  • Skip bulk-list mail using List-Id or Precedence: bulk/list.

  • Detect auto-generated mail via Auto-Submitted header.

5. Author templates in Make

Use short, respectful acknowledgments. Keep content factual and avoid promises. Two examples:

Template support_ack_v1

  • Subject: We received your support request

  • Body (HTML)

<p>Hi {{firstName}},</p>
<p>Thanks for contacting support. We created a ticket for your message titled “{{subject}}”.</p>
<p>Working hours: {{businessHours}}. Typical first response: {{slaHours}} hours.</p>
<p>Reference ID: {{caseId}}</p>
<p>For urgent items, reply with “URGENT” in the subject.</p>
<p>– {{teamName}}</p>

Template lead_ack_v1

  • Subject: Thanks for your interest

  • Body (HTML)

<p>Hi {{firstName}},</p>
<p>Thanks for reaching out about {{product}}. A teammate will send options shortly.</p>
<p>If helpful, pick a time here: {{bookingLink}}</p>
<p>– {{teamName}}</p>

6. Test the system

  • Send a message to each target address from an external mailbox.

  • Confirm label assignment in Gmail.

  • Run scenario once, then switch to “On”.

  • Verify one reply per thread and a single row in the log.

7. Harden production

  • Add a Make Array aggregator to collapse multiple triggers from the same thread that arrive within 60 seconds.

  • Add a Rate limiter (Tools → Sleep) when Sheets or Gmail throttles.

  • Log Reason for each skip path to diagnose filters later.

Code / JSON Snippets

Gmail filter queries

Apply label autobot/inbox with these examples. Edit domains.

(to:[email protected] OR to:[email protected])
 -from:*.yourdomain.com
 -from:noreply@*
 -from:mailer-daemon@*
(to:[email protected] OR subject:(demo OR pricing OR quote))
 -from:*.yourdomain.com
 -from:noreply@*
 -from:mailer-daemon@*

Gmail filter import (XML)

Import under Gmail → Settings → Filters → Import. Replace labels and addresses.

<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns='http://schemas.google.com/apps/2006'>
  <entry>
    <category term='filter'/>
    <title>Support AutoReply</title>
    <apps:property xmlns:apps='http://schemas.google.com/apps/2006' name='to' value='[email protected] OR [email protected]'/>
    <apps:property xmlns:apps='http://schemas.google.com/apps/2006' name='from' value='-noreply@* -mailer-daemon@*'/>
    <apps:property xmlns:apps='http://schemas.google.com/apps/2006' name='label' value='autobot/inbox'/>
    <apps:property xmlns:apps='http://schemas.google.com/apps/2006' name='shouldNeverSpam' value='true'/>
  </entry>
  <entry>
    <category term='filter'/>
    <title>Sales AutoReply</title>
    <apps:property xmlns:apps='http://schemas.google.com/apps/2006' name='to' value='[email protected]'/>
    <apps:property xmlns:apps='http://schemas.google.com/apps/2006' name='subject' value='demo OR pricing OR quote'/>
    <apps:property xmlns:apps='http://schemas.google.com/apps/2006' name='label' value='autobot/inbox'/>
  </entry>
</feed>

Make.com Code module: decision and template merge

Paste into a Code module. Inputs: the Gmail bundle. Outputs: one object.

// Helpers
const get = (o, k, d="") => (o && k in o) ? o[k] : d;
const hdr = (h, name) => {
  if (!h) return "";
  const k = Object.keys(h).find(x => x.toLowerCase() === name.toLowerCase());
  return k ? h[k] : "";
};
const emailRegex = /<?([A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,})>?/i;
const nameRegex = /^"?([^"<]+)"?\s*</;

// Config
const COOLDOWN_DAYS = 7;
const businessHours = "Mon–Fri 9:00–17:00 UTC";
const slaHours = "24";
const bookingLink = "https://cal.example.com/yourteam";
const teamName = "Your Team";
const product = "Your Product";

// Input
const msg = input; // Make passes Gmail fields on bundle
const headers = get(msg, "headers", {});
const from = hdr(headers, "From") || msg.from || "";
const subject = msg.subject || hdr(headers, "Subject") || "(no subject)";
const threadId = msg.threadId;
const messageId = hdr(headers, "Message-Id") || msg.id || "";
const listId = hdr(headers, "List-Id");
const precedence = hdr(headers, "Precedence");
const autoSubmitted = hdr(headers, "Auto-Submitted");

// Parse sender
const fromEmail = (from.match(emailRegex) || ["",""])[1];
const firstName = (from.match(nameRegex) || ["",""])[1].split(" ")[0] || fromEmail.split("@")[0];

// Safety checks
const lower = (fromEmail || "").toLowerCase();
const isNoReply = lower.includes("no-reply") || lower.includes("noreply");
const isDaemon = lower.includes("mailer-daemon") || lower.includes("postmaster");
const isList = (listId && listId.trim().length > 0) || /list|bulk/i.test(precedence);
const isAuto = /auto/i.test(autoSubmitted);

let reason = "ok";
if (!fromEmail) reason = "skip-no-from";
if (isNoReply) reason = "skip-no-reply";
if (isDaemon) reason = "skip-daemon";
if (isList) reason = "skip-list";
if (isAuto) reason = "skip-auto";

// Route
let templateId = "";
if (/sales@yourdomain\.com/i.test(msg.to || "") || /\b(demo|pricing|quote)\b/i.test(subject)) {
  templateId = "lead_ack_v1";
} else {
  templateId = "support_ack_v1";
}

// Merge templates
const caseId = "C-" + (Date.now() % 1000000).toString().padStart(6, "0");
const tokens = { firstName, subject, businessHours, slaHours, caseId, teamName, bookingLink, product };

const templates = {
  support_ack_v1: {
    subject: "We received your support request",
    body: `<p>Hi ${tokens.firstName},</p>
<p>Thanks for contacting support. We created a ticket for “${tokens.subject}”.</p>
<p>Working hours: ${tokens.businessHours}. Typical first response: ${tokens.slaHours} hours.</p>
<p>Reference ID: ${tokens.caseId}</p>
<p>– ${tokens.teamName}</p>`
  },
  lead_ack_v1: {
    subject: "Thanks for your interest",
    body: `<p>Hi ${tokens.firstName},</p>
<p>Thanks for reaching out about ${tokens.product}. A teammate will send options shortly.</p>
<p>You can also pick a time: <a href="${tokens.bookingLink}">${tokens.bookingLink}</a></p>
<p>– ${tokens.teamName}</p>`
  }
};

const tpl = templates[templateId];

// Output
return {
  shouldReply: reason === "ok",
  reason,
  templateId,
  subject: tpl.subject,
  body: tpl.body,
  threadId,
  messageId,
  fromEmail
};

Google Sheets search and cooldown check (optional)

Use this formula in a Google Sheets > Search rows module to enforce per-thread uniqueness on your side. If you also want per-sender cooldown, add another search where FromEmail = {{fromEmail}} AND LastRepliedAt > NOW()-COOLDOWN_DAYS.

Gmail “Reply to an email” mapping

  • Thread: {{Code[1].threadId}}

  • Body: {{Code[1].body}} (HTML enabled)

  • Subject: {{subject}} from the original, or {{Code[1].subject]}} if you prefer a titled ack

  • From: choose alias [email protected] or [email protected]

Sample workflow JSON code

This platform-agnostic JSON describes modules and guards. Adapt to Make or n8n.

{
  "workflow": {
    "name": "gmail-autoreply-free",
    "assumptions": {
      "gmailFiltersApplyLabel": true,
      "replyOncePerThread": true,
      "cooldownDays": 7
    },
    "nodes": [
      {
        "id": "trigger_gmail_watch",
        "type": "trigger.gmail.watch",
        "config": { "label": "autobot/inbox", "includeHeaders": true, "includeHtml": true, "includeText": true },
        "outputs": ["email"]
      },
      {
        "id": "code_decision",
        "type": "function.javascript",
        "inputs": ["email"],
        "config": { "source": "// Code from snippet above" },
        "outputs": ["decision"]
      },
      {
        "id": "sheets_lookup_thread",
        "type": "db.googlesheets.search",
        "inputs": ["decision.threadId"],
        "config": {
          "spreadsheet": "AutoReply_Log",
          "sheet": "AutoReply_Log",
          "where": "ThreadID = '{{decision.threadId}}'"
        },
        "outputs": ["thread_rows"]
      },
      {
        "id": "router_decide",
        "type": "router",
        "branches": [
          { "when": "decision.shouldReply && thread_rows.count == 0", "to": "gmail_reply" },
          { "when": "true", "to": "log_skip" }
        ]
      },
      {
        "id": "gmail_reply",
        "type": "mail.gmail.reply",
        "inputs": ["decision"],
        "config": {
          "threadId": "{{decision.threadId}}",
          "htmlBody": "{{decision.body}}",
          "subjectMode": "preserve"
        },
        "outputs": ["sent"]
      },
      {
        "id": "log_write",
        "type": "db.googlesheets.append",
        "inputs": ["decision", "sent"],
        "config": {
          "spreadsheet": "AutoReply_Log",
          "sheet": "AutoReply_Log",
          "values": {
            "ThreadID": "{{decision.threadId}}",
            "MessageID": "{{decision.messageId}}",
            "FromEmail": "{{decision.fromEmail}}",
            "FirstSeenAt": "{{now()}}",
            "LastRepliedAt": "{{now()}}",
            "TemplateID": "{{decision.templateId}}",
            "Reason": "{{decision.reason}}"
          }
        }
      },
      {
        "id": "log_skip",
        "type": "db.googlesheets.append",
        "optional": true,
        "inputs": ["decision"],
        "config": {
          "spreadsheet": "AutoReply_Log",
          "sheet": "AutoReply_Log",
          "values": {
            "ThreadID": "{{decision.threadId}}",
            "MessageID": "{{decision.messageId}}",
            "FromEmail": "{{decision.fromEmail}}",
            "FirstSeenAt": "{{now()}}",
            "LastRepliedAt": "",
            "TemplateID": "{{decision.templateId}}",
            "Reason": "{{decision.reason}}"
          }
        }
      },
      {
        "id": "slack_notify",
        "type": "notify.slack.send",
        "optional": true,
        "inputs": ["decision"],
        "config": {
          "channel": "#inbox",
          "text": "Auto-replied with {{decision.templateId}} to {{decision.fromEmail}}"
        }
      }
    ]
  }
}

Fallback: direct Gmail reply via HTTP (advanced)

If you prefer pure HTTP, use Google’s Gmail API through Make’s HTTP module. Replace placeholders. The API requires OAuth and base64url-encoded MIME.

POST https://gmail.googleapis.com/gmail/v1/users/me/messages/send
Authorization: Bearer YOUR_OAUTH_TOKEN
Content-Type: application/json

{
  "raw": "BASE64URL_ENCODED_MIME"
}

Example MIME skeleton for reply

Headers preserve the thread and avoid a new conversation.

Subject: Re: {{originalSubject}}
In-Reply-To: {{originalMessageId}}
References: {{originalMessageId}}
To: {{fromEmail}}
Content-Type: text/html; charset=UTF-8

<html><body>...your HTML here...</body></html>

Use Cases / Scenarios

  • Support acknowledgment: Promise a realistic first-response time and include a case ID.

  • After-hours leads: Confirm receipt and share a booking link.

  • Event campaigns: Create a temporary filter for promo replies, then disable post-campaign.

  • VIP routes: If From matches a list, skip auto-reply and alert humans only.

  • Multi-brand inboxes: Choose a template by To: domain and send from the matching alias.

Limitations / Considerations

  • Gmail send limits apply per account and per day. Plan within your domain policy.

  • Free Make plans have monthly operation caps. Heavy inboxes require a paid tier or batching.

  • Filters must stay precise. Broad filters cause off-target replies.

  • List traffic and auto-generated mail need strict skip logic.

  • Legal and compliance. Include the company address and unsubscribe where your jurisdiction requires in marketing contexts.

  • Language detection is basic unless you add a detection API.

  • HTML-only replies can trigger plain-text fallbacks. Provide simple markup.

Fixes (common pitfalls with solutions and troubleshooting tips)

  • Looping with other auto-responders: Check Auto-Submitted, Precedence, and List-Id. If set, skip. Add “X-Autobot: v1” header and skip when present.

  • Many duplicates: Confirm the Sheets lookup by ThreadID. Ensure the Gmail module is “Reply to” not “Send email.”

  • Reply sent to an alias instead of the sender: Map To as the original sender from message headers, not your alias. Use the Reply module, which preserves the thread.

  • Gmail throttling: Insert a 200–500 ms Sleep between replies during spikes.

  • Templates missing tokens: Keep defaults for all tokens in the Code module. If a token is empty, degrade gracefully.

  • Wrong route: Log Reason and TemplateID for each message. Review top subjects or recipient domains and refine filters.

  • HTML rendering issues: Validate with a simple <p> structure and absolute links. Avoid heavy CSS.

  • Timezone confusion in SLAs: Render business hours as “UTC” or include the region explicitly.

Budget Calculation

Assumptions. Replace with your real quotas.

Variables

  • M = monthly inbound messages that match filters.

  • O = operations per handled message. Typical path without Slack: Gmail Watch (1) + Code (1) + Sheets Search (1) + Gmail Reply (1) + Sheets Append (1) = 5. With Slack, 6.

  • OpsTotal = M × O.

Examples

  • Light inbox: M = 120, O = 5OpsTotal = 600. Fits many free plans.

  • Busy inbox: M = 800, O = 6OpsTotal = 4,800. Upgrade or reduce scope.

Gmail send-limit guard

  • Let S be daily send limit for your account type.

  • DailyReplies = M / 30. Keep DailyReplies ≤ 0.8 × S for safety.

  • Example: M = 900DailyReplies = 30. If S = 500 you are safe.

Storage

  • One Google Sheet row per replied thread. Negligible cost.

  • Annual rows ≈ M If you reply once per thread.

Cost control levers

  • Narrow filters to the most valuable routes.

  • Apply a cooldown per sender to reduce repeat replies.

  • Disable Slack notifications or batch them to summaries.

Conclusion

The Gmail Filters + Make.com pattern gives you a dependable “free auto-responder” for first-touch support and lead intake. Filters select candidates. Make decisions with explicit logic, reply in-thread, and write a log for audit. The design avoids loops, enables clear SLAs, and scales from dozens to hundreds of messages per month without servers. Extend it incrementally as your inbox grows.