Auto-draft replies from canned-response templates
Match incoming questions to a template and stage a draft reply in Gmail you only need to send.
Published Oct 21, 2025
Half of Northwind’s support@ mail asks the same three questions — opening
hours, how to reset a password, where to find an invoice. The answers never
change, but someone still retypes them, and the queue waits while they do.
This script keeps the stock answers in a spreadsheet and matches each incoming question to one. When a thread matches a template’s pattern, it stages a draft reply on the thread using that template’s text. The repetitive replies are written before anyone opens the inbox; the team just reviews and sends.
What you’ll need
- A
TemplatesGoogle Sheet with three columns:pattern,subjectandbody.patternis a regular expression matched (case-insensitively) against the message subject and body;subjectandbodyare the canned reply. The sheet ID goes in the config below. - A Gmail
supportlabel applied to incoming support threads, by filter or by hand. - Nothing else — the script creates the
support/draftedlabel itself.
The script
// Sheet of canned responses: columns pattern, subject, body.
const TEMPLATES_SHEET = '1abcTemplatesSheetId';
// Gmail search that selects threads to consider: unread support mail
// that has not already been drafted.
const SEARCH_QUERY = 'label:support is:unread -label:support/drafted';
// Label applied once a draft is staged, so threads are not drafted twice.
const DRAFTED_LABEL = 'support/drafted';
/**
* Matches unread support threads against canned-response templates and
* stages a draft reply for any that match.
*/
function draftCannedReplies() {
// 1. Load the templates and the threads to consider.
const templates = readSheet(TEMPLATES_SHEET);
const threads = GmailApp.search(SEARCH_QUERY);
if (!templates.length || !threads.length) {
Logger.log('No templates or no threads — nothing to do.');
return;
}
// Find or create the marker label once, outside the loop.
const drafted = GmailApp.getUserLabelByName(DRAFTED_LABEL)
|| GmailApp.createLabel(DRAFTED_LABEL);
for (const thread of threads) {
// 2. Combine the subject and body into one string to search.
const msg = thread.getMessages()[0];
const haystack = `${msg.getSubject()}\n${msg.getPlainBody()}`;
// 3. Find the first template whose regex matches. No match = skip.
const tpl = templates.find((t) => new RegExp(t.pattern, 'i').test(haystack));
if (!tpl) continue;
// 4. Stage the canned reply as a draft and label the thread.
thread.createDraftReply(tpl.body, { subject: tpl.subject });
thread.addLabel(drafted);
}
}
/**
* Reads a sheet's first tab into an array of row objects, keyed by the
* header row.
*/
function readSheet(id) {
const [h, ...rows] = SpreadsheetApp.openById(id)
.getSheets()[0]
.getDataRange()
.getValues();
return rows.map((r) => Object.fromEntries(h.map((k, i) => [k, r[i]])));
}
How it works
draftCannedRepliesloads the template sheet into an array of objects and searches Gmail for unreadsupportthreads that have not been drafted yet. If either list is empty it logs a message and stops.- It resolves the
support/draftedlabel once, creating it on first run. - For each thread it takes the first message and joins the subject and
plain-text body into a single
haystackstring to search. - It runs
Array.findover the templates, testing eachpatternas a case-insensitive regular expression. The first template that matches wins; if nothing matches, the thread is skipped and left for a human. - When a template matches,
createDraftReplystages the template’sbodyas a draft, overriding the subject line with the template’ssubject. - The thread is labelled
support/draftedso the next run leaves it alone. readSheetis a small helper that turns the first sheet tab into row objects keyed by the header row, so templates are read as{pattern, subject, body}.
Example run
The Templates sheet holds:
| pattern | subject | body |
|---|---|---|
password|reset|log ?in | Re: Resetting your password | Hi, you can reset your password from the Sign in page… |
invoice|receipt|billing | Re: Finding your invoice | Hi, your invoices live under Account > Billing… |
A customer emails:
Subject: Can’t log in
Hi, I’ve forgotten my password and can’t get into my account.
The body matches the first template’s pattern (log ?in), so a draft is staged
with the subject “Re: Resetting your password” and the password-reset text. The
thread gets the support/drafted label. A thread that matches no pattern — say
a bespoke complaint — is left untouched for an agent to handle personally.
Trigger it
Run this on a frequent time-based trigger so drafts appear soon after mail arrives:
- In the Apps Script editor open Triggers (the clock icon).
- Add a trigger for
draftCannedReplies, Time-driven, Minutes timer, every 10 minutes.
Ten minutes keeps the common replies waiting in the queue almost as fast as the questions arrive.
Watch out for
createDraftReplystages, never sends. Awadesh reviews each one in Gmail before hitting send — a template can land on a thread it does not quite fit, and the human catch is what makes that safe.- Template order matters.
Array.findreturns the first match, so put more specific patterns above broad ones in the sheet. - Patterns are real regular expressions. An unescaped
.or(will match more than you expect — test new patterns before relying on them, and escape literal punctuation. - The script reads only the first message in the thread, so a follow-up that introduces a new question on an old thread will not be re-matched.
- A blank or invalid
patterncell will throw whennew RegExpis called. Keep the sheet tidy and avoid empty rows below the data.
Related
Convert long email threads into a summary note
Collapse a thread's history into a Doc for handover — perfect for client transitions or vacation cover.
Updated Jun 6, 2026
Pull event RSVPs from emails into a Sheet
Parse yes/no replies to event invites and tally attendance automatically.
Updated Jun 2, 2026
Turn forwarded emails into project tasks
Forward to [email protected] and a row lands in the Projects sheet under the right client.
Updated May 30, 2026
Turn starred emails into a task list
Sync every starred thread into the Northwind Tasks sheet automatically.
Updated May 26, 2026
Alert when a label hits a backlog threshold
Warn the Northwind team in Slack when a Gmail label has more than N unread threads.
Updated Mar 31, 2026