n8n Gmail Draft Reply Not in the Same Thread? 3 Fixes (Thread ID, Subject, In-Reply-To)
A common n8n question: you build "AI drafts a reply, I review it", the Gmail Create Draft node runs, and the draft shows up as a brand-new email with no recipient, or it sits in your thread but lands as a separate conversation for the person you send it to. The n8n forum has several threads on exactly this (a new mail instead of a reply, threading lost in drafts, Create Draft doesn't create a real reply). The cause is almost always one of three things.
Gmail AI Email Assistant (Draft-Only) n8n template. Pay what you want, $0 is fine.
Paid option for bulk jobs: if you also run AI over hundreds of Google Sheets rows, the Pro Kit adds the Google Sheet header CSV, a 10-minute setup guide and an offline test kit (a local OpenAI, Google Sheets and Gmail mock with the 15-scenario runner), so you can watch the retries and resume work before you use a real API key. The Gmail template on this page stays free.
Gmail's own rule
Google's Gmail API guide says a draft or message is only part of a thread when all three hold: the threadId is set, the References and In-Reply-To headers follow RFC 2822, and the Subject headers match (Gmail API: Managing threads). Each fix below maps to one of those.
Fix 1: set Thread ID (and To Email) in the draft options
In the Gmail node, choose Resource Draft, Operation Create, then open Options and add Thread ID and To Email (n8n docs: Gmail draft operations). Thread ID comes from the trigger, for example {{ $('Gmail Trigger').item.json.threadId }}. Without To Email you get the "draft with no recipient" symptom. Put the sender's bare address there, not the whole "Jane Doe" <jane@example.com> string if your trigger includes a header label.
Fix 2: make the subject match exactly
In one forum thread the fix was simply that the draft used a different subject. Two quiet ways this breaks:
- The expression leaves the header label in (
Subject: Hello) or a trailing newline. A subject expression ending in\nappears in a posted workflow that hit this problem. - The AI writes its own subject line. Don't let the model touch the subject; build it in an expression.
{{ (() => {
const s = String($('Gmail Trigger').item.json.subject || '')
.replace(/^Subject:\s*/i, '').trim();
return /^re:/i.test(s) ? s : 'Re: ' + s;
})() }}
Fix 3: add In-Reply-To and References for the recipient's side
Fixes 1 and 2 usually put the draft in your conversation. Whether it threads in the recipient's mail client depends on the In-Reply-To and References headers, and the Gmail node's draft options have no custom-header field (there is a feature request for it). The workaround is to create the draft yourself with the Gmail API: a Code node builds the raw message, and an HTTP Request node posts it.
Code node (run once for each item; turn off Simplify on the Gmail Trigger so the headers are available):
const m = $('Gmail Trigger').item.json;
const H = m.headers || {};
const get = (name) => {
const k = Object.keys(H).find(x => x.toLowerCase() === name);
return k ? String(H[k]).replace(new RegExp('^' + name + ':\\s*', 'i'), '').trim() : '';
};
const msgId = get('message-id');
const refs = (get('references') + ' ' + msgId).trim();
const from = get('reply-to') || get('from');
const to = (from.match(/<([^>]+)>/) || [null, from])[1].trim();
let subj = get('subject');
if (!/^re:/i.test(subj)) subj = 'Re: ' + subj;
const enc = (s) => /^[\x20-\x7E]*$/.test(s)
? s : '=?UTF-8?B?' + Buffer.from(s).toString('base64') + '?=';
const body = $json.reply_body; // your AI's reply text
const raw = [
'To: ' + to,
'Subject: ' + enc(subj),
'In-Reply-To: ' + msgId,
'References: ' + refs,
'Content-Type: text/plain; charset="UTF-8"',
'',
body,
].join('\r\n');
return { json: {
threadId: m.threadId,
raw: Buffer.from(raw).toString('base64')
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''),
}};
HTTP Request node: method POST, URL https://gmail.googleapis.com/gmail/v1/users/me/drafts, Authentication Predefined Credential Type and your Gmail OAuth2 credential, JSON body:
{ "message": { "raw": "{{ $json.raw }}", "threadId": "{{ $json.threadId }}" } }
This matches Google's threading requirements: threadId inside the message, RFC 2822 reply headers and the same subject. Test it by replying from a second account and checking that the reply lands in the original conversation on both sides.
In-Reply-To. If it isn't there, the recipient's client may still split the conversation, no matter how good the "Re:" looks (a point made in this forum post about the Send Email node).Want the whole draft-only assistant?
Our n8n Gmail AI agent that drafts replies guide covers the rest: pre-filtering newsletters, approved answers from Google Sheets, guardrails that stop drafts promising discounts, one label per email and a daily digest. The template is a free download there, and on Gumroad as pay-what-you-want with the setup guide: Gmail AI Email Assistant (Draft-Only) n8n template.
Free: AI Prompt Pack for Wholesalers & Job Seekers
10 copy-paste prompts from our paid packs: seller lead triage, underwriting, objection replies, job fit scoring, honest cover notes and interview prep. Free download (PDF); Gumroad asks for your email so it can send you the file.
Get the free prompt packNo email? Download the PDF directly.
Gmail AI Email Assistant (Draft-Only) n8n template. Pay what you want, $0 is fine.
FAQ
Why does my n8n Gmail draft show as a new email instead of a reply?
Usually the Thread ID option isn't set, or the draft's subject doesn't match the original subject exactly. Gmail requires the threadId, matching subjects and RFC 2822 In-Reply-To and References headers.
Can the n8n Gmail node set In-Reply-To headers on a draft?
The Create Draft options list no custom-header field. You can build the raw message in a Code node and create the draft with an HTTP Request to the Gmail API drafts endpoint, using your Gmail OAuth2 credential.
Why does my draft have no recipient?
Create Draft doesn't infer the recipient from the thread. Add the To Email option and map the sender's address from the trigger.