Create Gmail Draft
Create a draft email in Gmail (does not send)
Source Code
import fs from "fs";
const [
to = "",
subject = "",
body = "",
threadId = "",
outputPath = "session/draft-result.json",
htmlBody = "",
inReplyTo = "",
cc = "",
bcc = "",
] = process.argv.slice(2);
if (!to) {
console.error("Recipient (to) is required");
console.log(JSON.stringify({ success: false, error: "missing_recipient" }));
process.exit(1);
}
if (!subject && !threadId) {
console.error("Subject is required for new emails");
console.log(JSON.stringify({ success: false, error: "missing_subject" }));
process.exit(1);
}
console.log(`Creating draft to: ${to}`);
if (cc) console.log(`CC: ${cc}`);
if (bcc) console.log(`BCC: ${bcc}`);
console.log(`Subject: ${subject || "(reply)"}`);
if (htmlBody) console.log(`Format: HTML + plain text`);
if (inReplyTo) console.log(`In-Reply-To: ${inReplyTo}`);
/**
* Build RFC 2822 formatted email message
* Supports plain text, HTML (multipart/alternative), CC/BCC, and reply threading headers
*/
function buildRawMessage(to, subject, body, options = {}) {
const { htmlBody, inReplyTo, cc, bcc } = options;
const lines = [];
// Required headers
lines.push(`To: ${to}`);
if (cc) lines.push(`Cc: ${cc}`);
if (bcc) lines.push(`Bcc: ${bcc}`);
lines.push(`Subject: ${subject}`);
// Reply threading headers
if (inReplyTo) {
lines.push(`In-Reply-To: ${inReplyTo}`);
lines.push(`References: ${inReplyTo}`);
}
lines.push("MIME-Version: 1.0");
if (htmlBody) {
// Multipart message with both plain text and HTML
const boundary = `boundary_${Date.now()}_${Math.random().toString(36).slice(2)}`;
lines.push(`Content-Type: multipart/alternative; boundary="${boundary}"`);
lines.push("");
// Plain text part
lines.push(`--${boundary}`);
lines.push("Content-Type: text/plain; charset=utf-8");
lines.push("");
lines.push(body);
lines.push("");
// HTML part
lines.push(`--${boundary}`);
lines.push("Content-Type: text/html; charset=utf-8");
lines.push("");
lines.push(htmlBody);
lines.push("");
lines.push(`--${boundary}--`);
} else {
// Plain text only
lines.push("Content-Type: text/plain; charset=utf-8");
lines.push("");
lines.push(body);
}
return Buffer.from(lines.join("\r\n"))
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
}
try {
const raw = buildRawMessage(to, subject, body, { htmlBody, inReplyTo, cc, bcc });
const requestBody = {
message: { raw },
};
if (threadId) {
requestBody.message.threadId = threadId;
}
const res = await fetch(
"https://gmail.googleapis.com/gmail/v1/users/me/drafts",
{
method: "POST",
headers: {
Authorization: "Bearer PLACEHOLDER_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify(requestBody),
}
);
if (!res.ok) {
const errorText = await res.text();
console.error(`Gmail API error: ${res.status}`);
console.error(errorText);
throw new Error(`Failed to create draft: ${res.status}`);
}
const draft = await res.json();
// Write result
const dir = outputPath.split("/").slice(0, -1).join("/");
if (dir) fs.mkdirSync(dir, { recursive: true });
const output = {
success: true,
createdAt: new Date().toISOString(),
draftId: draft.id,
messageId: draft.message?.id,
threadId: draft.message?.threadId,
to,
subject,
...(cc && { cc }),
...(bcc && { bcc }),
...(htmlBody && { hasHtml: true }),
...(inReplyTo && { inReplyTo }),
};
fs.writeFileSync(outputPath, JSON.stringify(output, null, 2));
console.log(`\n✓ Draft created`);
console.log(` Draft ID: ${draft.id}`);
console.log(` Open Gmail to review and send.`);
console.log(JSON.stringify(output));
} catch (error) {
console.error("Failed to create draft:", error.message);
throw error;
}