DIY Guide — Free & Self-Hosted

Build your own WIP tracker
with tools you already have

If you'd rather not sign up for a new product and are comfortable spending a Sunday afternoon on it, here's how to build a functional AO3 update tracker using only Gmail and free Google tools.

This guide is published openly because it's the right thing to do. If you'd rather skip the setup, WIP Inbox handles all of this automatically — plus a proper dashboard, reading progress tracking, and more. But if you want to build it yourself, everything you need is below.

What you'll buildA simple, private tracker

A Gmail filter that catches AO3 subscription update emails and forwards them to a Google Apps Script, which parses the story title and chapter number and logs them into a Google Sheet. Your Sheet becomes your WIP tracker — sortable, searchable, and entirely inside your own Google account.

Cost: $0. Accounts needed: just Google. Time: roughly 45–60 minutes if you follow the steps carefully.


Step 01Set up your Google Sheet

Create a new Google Sheet. Name it whatever you like — "WIP Tracker" works fine. Set up the following columns in row 1, in this exact order:

ColumnHeader textWhat goes here
AStory TitleParsed from the email subject line
BLatest ChapterMost recent chapter number received
CLast UpdatedDate the update email arrived
DLast Chapter ReadFill this in manually as you read
EAO3 LinkOptional — paste the story URL here manually
FNotesWhatever you want — status, thoughts, reminders

Copy the Sheet's ID from the URL — it's the long string between /d/ and /edit. You'll need it in the next step.


Step 02Add the Apps Script

In your Sheet, go to Extensions → Apps Script. Delete any placeholder code in the editor and paste in the following:

Code.gs — paste this in full
// ── CONFIG ──────────────────────────────────────────────
// Paste your Google Sheet ID here (from the URL)
const SHEET_ID = 'YOUR_SHEET_ID_HERE';

// The tab name inside your Sheet
const SHEET_TAB = 'Sheet1';

// How far back to look for emails on first run (adjust if needed)
const DAYS_BACK = 1;
// ────────────────────────────────────────────────────────

function processAO3Updates() {
  const sheet = SpreadsheetApp
    .openById(SHEET_ID)
    .getSheetByName(SHEET_TAB);

  // Search Gmail for AO3 update emails from the last DAYS_BACK days
  const cutoff = new Date();
  cutoff.setDate(cutoff.getDate() - DAYS_BACK);
  const dateStr = Utilities.formatDate(cutoff, 'UTC', 'yyyy/MM/dd');

  const query = `from:do-not-reply@archiveofourown.org after:${dateStr} subject:"posted Chapter"`;
  const threads = GmailApp.search(query);

  if (threads.length === 0) return;

  // Load existing story titles to avoid duplicate rows
  const data = sheet.getDataRange().getValues();
  const existing = {};
  for (let i = 1; i < data.length; i++) {
    if (data[i][0]) existing[data[i][0].toLowerCase()] = i + 1;
  }

  threads.forEach(thread => {
    thread.getMessages().forEach(msg => {
      const subject = msg.getSubject();

      // Parse chapter number and story title from subject line
      // Expected format: [AO3] AuthorName posted Chapter X of Story Title
      const match = subject.match(/posted Chapter (\d+) of (.+)$/i);
      if (!match) return;

      const chapterNum = parseInt(match[1], 10);
      const title = match[2].trim();
      const date = msg.getDate();
      const titleKey = title.toLowerCase();

      if (existing[titleKey]) {
        // Story already exists — update chapter and date
        const row = existing[titleKey];
        const currentChapter = sheet.getRange(row, 2).getValue();
        if (chapterNum > currentChapter) {
          sheet.getRange(row, 2).setValue(chapterNum);
          sheet.getRange(row, 3).setValue(date);
        }
      } else {
        // New story — add a row
        sheet.appendRow([title, chapterNum, date, 0, '', '']);
        existing[titleKey] = sheet.getLastRow();
      }
    });
  });
}

Replace YOUR_SHEET_ID_HERE with the ID you copied in Step 1. If your Sheet tab isn't named "Sheet1", update SHEET_TAB to match.

Click Save (the floppy disk icon), then click Run to test it. The first time you run it, Google will ask you to authorize the script to access Gmail and Sheets — this is normal. Approve it.

First run warning: if you've been subscribed to AO3 stories for a while, this first run may pull in a lot of old emails depending on your DAYS_BACK setting. Set it to 1 for the first run, check your Sheet looks right, then adjust upward if you want to backfill older updates.

Step 03Set up the trigger

The script needs to run automatically so your Sheet stays updated without you touching it.

  1. In the Apps Script editor, click the clock icon in the left sidebar (Triggers)
  2. Click + Add Trigger in the bottom right
  3. Set: Function → processAO3Updates, Event source → Time-driven, Type → Hour timer, Interval → Every hour
  4. Click Save

The script will now check for new AO3 emails once an hour and update your Sheet automatically.


Step 04(Optional) Add a dashboard with AppSheet

If you want something more readable than a raw spreadsheet, Google's AppSheet can turn your Sheet into a simple mobile-friendly app for free. Connect your Sheet, set "Last Updated" as the sort field, and you'll have a sortable reading list you can use from your phone.

This step is genuinely optional — plenty of people find the Sheet itself is enough.


Known issuesThings to watch for

AO3 emails going to spam

Gmail occasionally routes AO3 update emails to spam. The script only searches your inbox by default. If updates are being missed, create a Gmail filter: From: do-not-reply@archiveofourown.org → Never send to spam. This also stops them cluttering your inbox.

Story titles with "Chapter" in the name

If a story title starts with the word "Chapter" (e.g. "Chapterhouse"), the regex may misparse it. It's an edge case but worth knowing. You'd need to manually correct the Sheet row if it happens.

Authors who don't include chapter totals

Many AO3 authors leave their chapter count as "?" while writing. The script handles this fine — it only tracks the chapter number from the update email, not the total planned. You won't know how far a story is from completion unless the author sets a total.

The script doesn't know what you've read

Column D ("Last Chapter Read") is manual — you fill it in yourself. The script only updates "Latest Chapter" and "Last Updated." Sorting by the gap between B and D tells you what needs catching up, but you're the one keeping track of where you left off.

Trigger authorization expiry

Google occasionally revokes script authorization, especially if you haven't used the Sheet in a while. If updates stop appearing, go back to Apps Script and re-run manually — it'll prompt you to re-authorize if needed.

Why this guide exists

I built WIP Inbox because I was losing track of fics I loved and couldn't find anything that solved it the way I wanted. But I know not everyone wants to sign up for a new product — some people just want to solve the problem themselves, with tools they already control.

This guide is for those people. The Apps Script version above is a genuinely good personal solution: free, private, runs entirely inside your own Google account, and you own all your data outright.

WIP Inbox goes further — automatic progress tracking, a proper reading dashboard, a completed library, and no spreadsheet wrangling. If that sounds useful, it's at wipinbox.com. If this guide is enough for you — great. That's a win too.

This guide is free to share, adapt, and improve. No rights reserved. — Katie