appscript.dev
Automation Beginner Gmail Sheets

Turn starred emails into a task list

Sync every starred thread into the Northwind Tasks sheet automatically.

Published May 26, 2026

A star is the fastest way to say “I need to deal with this” — one click and you move on. But the inbox is a poor task list. Stars pile up, scroll out of view, and mix with everything else, so the thing you starred to remember is the thing you forget.

At Northwind, Awadesh stars threads that turn into work and wants them to land in a proper Tasks sheet without any extra effort. This script does the sync: every few minutes it finds newly starred threads, copies each into the Tasks sheet with a clickable link back to the email, and marks the thread as captured so it is never added twice. Starring stays the one-click habit; the tracking happens on its own.

What you’ll need

  • A Google Sheet with a Tasks tab. The script writes four columns in this order: title, source, link, status. Add those headers in row 1.
  • The spreadsheet’s ID, taken from its URL, set in the config below.
  • Nothing else — the script creates the tasks/captured label itself.

The script

// The spreadsheet that holds the task list.
const TASKS_SHEET_ID = '1abcTasksSheetId';

// The label applied to a thread once it has been synced, so the same
// starred thread is never captured twice.
const CAPTURED_LABEL = 'tasks/captured';

/**
 * Finds starred threads that haven't been captured yet and appends one
 * row per thread to the Tasks sheet, with a link back to the email.
 * Designed to run on a short time-driven trigger.
 */
function syncStarredToTasks() {
  const sheet = SpreadsheetApp.openById(TASKS_SHEET_ID).getSheets()[0];

  // 1. Read column C (the link column) to know what's already captured.
  const known = new Set(
    sheet.getRange('C2:C').getValues().flat().filter(Boolean)
  );

  // 2. Find starred threads not yet carrying the captured label.
  const starred = GmailApp.search(`is:starred -label:${CAPTURED_LABEL}`);
  if (starred.length === 0) {
    Logger.log('No new starred threads to sync.');
    return;
  }

  // 3. Find the captured label, creating it on first run.
  const captured = GmailApp.getUserLabelByName(CAPTURED_LABEL)
    || GmailApp.createLabel(CAPTURED_LABEL);

  // 4. Build a row for each new starred thread.
  const rows = [];
  for (const thread of starred) {
    const link = `https://mail.google.com/mail/u/0/#inbox/${thread.getId()}`;

    // Skip if the link is already in the sheet — a second guard against
    // duplicates alongside the captured label.
    if (known.has(link)) continue;

    rows.push([thread.getFirstMessageSubject(), 'gmail', link, 'open']);
    thread.addLabel(captured);
  }

  // 5. Append all the new rows in one write.
  if (rows.length) {
    sheet.getRange(sheet.getLastRow() + 1, 1, rows.length, 4).setValues(rows);
  }
  Logger.log(`Synced ${rows.length} starred thread(s) to Tasks.`);
}

How it works

  1. The script opens the Tasks sheet and reads column C — the link column — into a Set. That set is the record of which emails have already become tasks.
  2. It searches Gmail for starred threads that do not carry the tasks/captured label. If there are none, it logs a message and stops before doing any more work.
  3. It looks up the tasks/captured label and creates it on the first run, so there is no manual label setup.
  4. For each new starred thread it builds a direct link to the email and a task row: the subject of the first message as the title, gmail as the source, the link, and a status of open. It also applies the captured label to the thread.
  5. The link is checked against the known set before the row is built — a second guard so that even if a label is removed by hand, an already-synced email is not duplicated.
  6. All the new rows are appended to the sheet in a single write, which is faster and tidier than appending one row at a time.

Example run

Awadesh stars two threads during the morning: “Contract review — Belmont” and “Logo files for handover”. On the next run the script finds both, since neither carries the captured label yet, and appends:

titlesourcelinkstatus
Contract review — Belmontgmailhttps://mail.google.com/mail/u/0/#inbox/18fopen
Logo files for handovergmailhttps://mail.google.com/mail/u/0/#inbox/18gopen

Both threads are now labelled tasks/captured. On the next run they are skipped, so re-starring or re-opening them does not create duplicate rows.

Trigger it

This should feel close to instant, so run it on a short interval:

  1. In the Apps Script editor, open Triggers (the clock icon).
  2. Click Add Trigger.
  3. Choose syncStarredToTasks, event source Time-driven, type Minutes timer, and pick Every 5 minutes.
  4. Save, and approve the authorisation prompt the first time it runs.

Watch out for

  • A thread is captured once. After the tasks/captured label is applied, un-starring and re-starring the same thread does nothing — the row already exists. That is intentional, but it means you cannot “re-add” a task by starring it again.
  • The link uses thread.getId() against the #inbox/ path. If the thread has been archived or moved out of the inbox, the link still resolves in Gmail, but the path is cosmetic rather than exact.
  • The duplicate check matches the link string exactly. If you edit a link in the sheet, the guard will no longer recognise it and the thread could be re-added if its label is also removed.
  • The script only ever appends rows — it never updates or removes them. If you complete a task, change its status in the sheet by hand; the script will not touch existing rows.
  • The title is the subject of the first message in the thread. A long thread that drifted off its original topic will still be titled after where it started.

Related