Having a system that automatically responds to incoming email messages can be a great time saver. In this post you will learn how to create such a system for Gmail, using Google Apps Script.
Business requirements
When designing the solution, I've had the following business requirements:
- Create a trigger to have the script run automatically every minute
- Make it easy to define what kinds of emails to respond to
- Ensure that emails don't get duplicate replies even if they remain in the inbox
- Log how many emails received a reply in every run
The script
You can download the complete script from here. For those who want more information, I will go through the code below.
The first thing I like to do is to create a global config object that is used to keep some definitions that are used throughout the script. I place the object at the top of the file, which will make it easy for you to find the definitions and change them as needed.
// Manage script configuration here
const g = {
subjectFilter: 'New inquiry',
labelName: 'Auto-replied',
textBody: `Hi,
Thank you for contacting us. This is an auto-response system.
One of our agents will contact you shortly.`,
htmlBody: `<p>Hi,</p>
<p>Thank you for contacting us. This is an auto-response system.
One of our agents will contact you shortly.</p>`,
maxThreads: 100,
startingThread: 0,
replyCount: 0,
};
g
is the global object.
subjectFilter
contains the subject keywords that the
script should respond to. In other words, the script will reply
only to emails whose subject line contains these keywords.
labelName
is the Gmail label that the script assigns
to the replied email. We use the label name alongside
subjectFilter
to focus only on new emails. This will
prevent sending duplicate replies to each email.
textBody
and htmlBody
contain the texts
that the script will inject into the reply.
The next two keys have to do with handling many threads. Google
Apps Script has a limit on the number of email threads that can be
fetched at one time. If we have a lot of threads then we need to
pull them in several requests, by paging through them, like paging
through search results. maxThreads
specifies how many
threads to pull at one time, and startingThread
is
akin to a page number: fetch from thread zero, then from thread
one hundred, and so on.
Lastly, replyCount
sums up the number of emails that
receive a reply.
Initializing the automation
Every time the script runs, it performs an initialization for the entire automation. Here's the code for that:
function init_() {
const labels = GmailApp.getUserLabels().map((l) => l.getName());
if (!labels.includes(g.labelName)) {
GmailApp.createLabel(g.labelName);
}
g.label = GmailApp.getUserLabelByName(g.labelName);
const queryFilter = {
subject: g.subjectFilter,
'-label': g.labelName,
};
g.query = Object.entries(queryFilter)
.map((e) => e.join(':'))
.join(' ');
const triggers = ScriptApp.getProjectTriggers();
if (triggers.length == 0) {
ScriptApp.newTrigger('checkAndReply')
.timeBased().everyMinutes(1).create();
}
}
First, the script checks if Gmail already has a user-generated
label of the name defined in g.labelName
. If it
doesn't then it creates a label, and stores a reference to it in
g.label
.
Next, we define a filter to limit the scope of the threads we want
Gmail to search for. We want threads with subject lines that
contain our key words, as well as threads that haven't been
labeled with our label. g.query
takes the keys and
values of the queryFilter
object and joins them into
a string that GmailApp.search can use.
Finally, we want this script to run automatically every minute. To do so, we check whether the script already has a trigger (from a prior run), and if not, then we create one.

The main body
Now we get to the main part of the automation: checking for new emails, replying to them, and labeling them to avoid duplicate replies.
First, we declare the main function that the trigger will run and execute the initialization.
function checkAndReply(){
init_();
}
Next, we create a loop to iterate through the threads. We ask GmailApp to search for threads that meet our filtering criteria, starting at a specific thread number and retrieving our maximum threads threshold.
We increment our starting thread number by the maximum threads, and we check if the number of returned threads is less than our maximum threads count. If it is then that's our sign that there are no more threads to page through and we break out of the loop.
function checkAndReply() {
init_();
while (true) {
const threads = GmailApp.search(g.query, g.startingThread, g.maxThreads);
g.startingThread += g.maxThreads;
if (threads.length < g.maxThreads) {
break;
}
}
}
Next, we handle each of the returned threads separately. We reply to it using our text strings, and then label the thread. We also increment the count of emails we've replied to.
threads.forEach((thread) => {
thread.reply(g.textBody, { htmlBody: g.htmlBody });
g.label.addToThread(thread);
g.replyCount++;
});
The final piece is to log out the number of emails replies we sent this time around:
const text =
g.replyCount == 0
? 'No new emails found.'
: g.replyCount == 1
? 'Replied to one new email.'
: `Replied to ${g.replyCount} new emails.`;
console.log(text);
Further customization
Again, you can download the complete script from here. The script can be easily modified to handle more complex logic. You could, for instance:
- Create an out-of-office responder for nights and weekends
- Use different reply texts for different emails based on some business criteria, such as subject keywords or sender information
- Store the text in Google Docs so that non-developers can modify the replies without handling the code
- Log the transactions to a Google Sheet for more in-depth analysis