// Gmail · Apps Script

Create a draft reply in Gmail.

How to create a draft reply that stays inside an existing Gmail thread using Apps Script, and why GmailApp.createDraft silently breaks the conversation instead.

I want to programmatically draft a reply to an email and have it appear in the same thread, not as a new conversation.

The script

copy · paste · trigger
createDraftReply.gs
Apps Script
// Creates a draft reply inside the original thread.
// Run once manually or call from a trigger.
function draftReplyToLatest() {
  var label = GmailApp.getUserLabelByName('needs-reply');
  var threads = label.getThreads(0, 5);

  for (var i = 0; i < threads.length; i++) {
    var thread = threads[i];
    var latest = thread.getMessages();
    var last = latest[latest.length - 1];

    var body = 'Hi ' + last.getFrom() + ', Thanks for your message. I will follow up shortly. Best,';

    thread.createDraftReply(body);
  }
}

Need a variant? Gnaw writes a custom version from one sentence — fields, triggers, edge cases handled.

Walkthrough

The one method that matters

GmailThread has a method called createDraftReply. It takes a plain-text body string, sets the To address to whoever sent the last message in the thread, prefixes the subject with 'Re:' if it isn't already there, and inserts the draft into the same thread. The draft appears in Gmail exactly as if you had clicked Reply and then saved.

The alternative most people reach for first is GmailApp.createDraft. That method creates a standalone draft with no thread association. When you send it, Gmail treats it as a new conversation. The recipient sees a fresh subject line, their mail client doesn't group it with the original, and your Sent folder splits the history. I have watched this bite people who built automated follow-up tools and only noticed the problem weeks later when a client asked why they were getting duplicate threads.

The fix is always the same: get the GmailThread object first, then call createDraftReply on it, not on GmailApp.

Getting the thread object in practice

The most common starting points are a label search, a full-text search via GmailApp.search, or a message ID you stored earlier. In the snippet above, threads come from a label named 'needs-reply'. GmailApp.search('label:needs-reply') returns the same set if you prefer that form.

Once you have the thread, thread.getMessages() returns an array of GmailMessage objects in chronological order. The last element is the most recent message. Calling last.getFrom() gives you the sender's address in 'Name <email>' format, which is safe to drop into the body but not into a To: header directly. If you need a clean address for createDraftReplyAll or similar, use last.getFrom().match(/&lt;(.+?)&gt;/) to pull just the angle-bracket portion.

createDraftReply returns a GmailDraft object. You can chain .send() on it immediately if you want to skip the review step, but for any outbound-facing automation I keep the draft stage. One bad regex in the body field and you would rather catch it in Drafts than in Sent.

Quota limits and batch discipline

Apps Script Gmail quotas are per-day and per-account type. Consumer Google accounts get 100 email-related calls per day (createDraftReply counts against this). Google Workspace accounts get 1,500. If your script loops over a large label, it will throw a 'Service invoked too many times' error mid-run and leave some threads without drafts, with no indication of where it stopped.

The standard guard is to check MailApp.getRemainingDailyQuota() before the loop and bail early if the remaining count is less than the number of threads you intend to process. Better still, process in small batches via a time-based trigger and store progress in PropertiesService so a quota stop is recoverable. The first time I hit this in production, the script had silently drafted 40 out of 200 threads; nothing failed loudly, the rest just never happened.

Want a custom version?

Describe your sheet and the rule you want. Gnaw writes the Apps Script — fields, triggers, edge cases — in one shot.

FAQ

4 questions
Can I set the Reply-To or CC fields with createDraftReply?
Yes. The full signature is thread.createDraftReply(body, options) where options is an object that accepts replyTo, cc, bcc, and htmlBody. Pass htmlBody to send HTML instead of plain text; if you include both body and htmlBody, Gmail uses htmlBody for clients that support it and body as the fallback.
Why does my draft show up as a new thread instead of a reply?
You called GmailApp.createDraft instead of thread.createDraftReply. GmailApp.createDraft has no thread context and always produces a new conversation. Confirm by logging the method name you are calling; the two look similar enough to mix up when autocomplete fills it in.
How do I reply to a specific message in the thread rather than the last one?
Use message.createDraftReply(body, options) on the GmailMessage object directly instead of calling it on the thread. This replies to that specific message and still keeps the draft inside the original thread.
Does createDraftReply count against Gmail send quotas?
Creating a draft does not consume your daily email send quota. It does count against the general Gmail API read/write quota. Calling .send() on the returned GmailDraft object later is what consumes a send quota unit.