Summarize long email threads into three bullets
Collapse Northwind threads to three bullets — for fast handovers and weekly digests.
Published Jul 10, 2025
A twenty-message email thread holds three useful facts: where things stand, what happens next, and who owes what. Everything else is greeting lines, quoted history, and “thanks, will do”. When a Northwind account changes hands or a manager wants a Friday digest, somebody still has to scroll the whole thread to extract those three facts.
This function does the extracting. Give it a thread ID and it stitches every message into a transcript, asks Claude to boil it down to exactly three bullets, and hands the summary back. It is the building block for a handover note or a weekly digest — wrap it in a loop and you have both.
What you’ll need
- A Gmail account with the threads you want summarised — you’ll pass each thread’s ID to the function.
- An Anthropic API key saved as
ANTHROPIC_API_KEYin Script Properties — see Store API keys and secrets securely. - Nothing else — this is a single function you call with a thread ID.
The script
// The three things every summary must answer. Edit this list to
// retune what the bullets cover.
const SUMMARY_POINTS = ['current status', "what's next", 'who owes what'];
// Cap the transcript so a very long thread cannot blow the token budget.
const MAX_TRANSCRIPT_CHARS = 12000;
/**
* Summarises a Gmail thread into exactly three bullet points.
*
* @param {string} threadId - The Gmail thread ID to summarise.
* @return {string} A three-bullet summary, or '' if the thread is empty.
*/
function summariseThread(threadId) {
// 1. Load the thread; bail out if the ID does not resolve.
const thread = GmailApp.getThreadById(threadId);
if (!thread) {
Logger.log('No thread found for ID ' + threadId);
return '';
}
// 2. Stitch every message into a labelled transcript.
const transcript = thread.getMessages()
.map((m) =>
`From ${m.getFrom()} on ${m.getDate().toISOString()}:\n${m.getPlainBody()}`)
.join('\n\n---\n\n')
.slice(0, MAX_TRANSCRIPT_CHARS);
if (!transcript.trim()) {
Logger.log('Thread has no readable content — nothing to summarise.');
return '';
}
// 3. Ask Claude for exactly three bullets, one per summary point.
const prompt =
'Summarise this Northwind email thread into exactly three bullet points: ' +
SUMMARY_POINTS.join(', ') + '. ' +
'One bullet per point, in that order. No preamble.\n\n' +
transcript;
// 4. Haiku is enough for summarisation — fast and cheap.
return callClaude(prompt);
}
/**
* Minimal Anthropic API call. The key lives in Script Properties — it
* is never pasted into the code.
*/
function callClaude(prompt, model = 'claude-haiku-4-5-20251001', maxTokens = 300) {
const key = PropertiesService.getScriptProperties()
.getProperty('ANTHROPIC_API_KEY');
const res = UrlFetchApp.fetch('https://api.anthropic.com/v1/messages', {
method: 'post',
contentType: 'application/json',
headers: { 'x-api-key': key, 'anthropic-version': '2023-06-01' },
payload: JSON.stringify({
model,
max_tokens: maxTokens,
messages: [{ role: 'user', content: prompt }],
}),
muteHttpExceptions: true,
});
return JSON.parse(res.getContentText()).content[0].text.trim();
}
How it works
summariseThreadloads the thread by ID. If the ID does not resolve — a deleted or mistyped thread — it logs the miss and returns an empty string.- It maps every message to a
From … on …:header followed by its plain-text body, then joins them with a divider into one transcript. - It trims the transcript to
MAX_TRANSCRIPT_CHARSso a very long thread cannot push past the model’s input budget. If the trimmed transcript is empty, it stops early. - It builds a prompt that asks for exactly three bullets, one for each entry in
SUMMARY_POINTS, in a fixed order. - It calls Claude Haiku — summarisation does not need a reasoning model, and Haiku is fast and cheap enough to run across a whole inbox.
- It returns the bullet text so the caller can log it, email it, or collect it into a digest.
Example run
Call it from the editor or another function with a thread ID:
const summary = summariseThread('18c2a1f9e4b0d3a7');
Logger.log(summary);
For a long back-and-forth about a delayed Northwind invoice, the returned string looks like this:
- Status: The April invoice was disputed over a duplicate line item;
finance has confirmed the duplicate and agreed a credit note.
- What's next: A corrected invoice goes out by Friday; the customer pays
within their usual 14-day terms once received.
- Who owes what: Northwind finance owes the corrected invoice; the
customer owes payment after it arrives.
Three bullets that answer the only questions a handover needs — instead of scrolling twenty messages to find them.
Run it
This is an on-demand building block. Wrap it in a loop to build a weekly digest of every thread under a label:
function weeklyDigest() {
const threads = GmailApp.search('label:accounts newer_than:7d');
if (!threads.length) return;
const digest = threads
.map((t) => `### ${t.getFirstMessageSubject()}\n${summariseThread(t.getId())}`)
.join('\n\n');
GmailApp.sendEmail(
Session.getActiveUser().getEmail(),
'Weekly account digest',
digest);
}
Add a weekly time-driven trigger for weeklyDigest and the summary lands in
your inbox every Friday.
Watch out for
- The summary is only as good as the thread. If the messages never say who owes what, Claude will guess or hedge — that is a sign the thread itself was unclear, not a bug.
- Long threads are trimmed to
MAX_TRANSCRIPT_CHARS, which drops the oldest messages first. If the early history matters, raise the cap andmax_tokenstogether, or summarise in batches. - Quoted reply chains inflate the transcript — the same text repeats in every message. The character cap absorbs most of this, but a cleaner summary comes from stripping quoted lines before sending.
- Running this across a large label hits one API call per thread. For a big digest, watch the six-minute execution limit and split the work across runs if needed.
getPlainBodydrops formatting and attachments. A thread whose decision lives in a spreadsheet attachment will summarise as “see attached” — the script cannot read what it cannot see.
Related
Summarize chat and Slack exports
Digest Northwind's long Slack conversations into recaps — for catch-up after PTO.
Updated Dec 5, 2025
Digest daily news into a personal briefing
Summarise headlines on a schedule — Northwind morning briefing for Awadesh.
Updated Nov 19, 2025
Summarize pros and cons from reviews
Extract what Northwind customers love and hate about each product into a single roll-up.
Updated Nov 3, 2025
Summarize YouTube videos into notes
Turn transcripts into Northwind study summaries — one Doc per video.
Updated Oct 6, 2025
Build a competitor-mention monitor
Summarise what Northwind's rivals are doing each week — feeds in, summary out.
Updated Sep 12, 2025