Build Your Own DIY Version

Completely free. Uses only Google tools you probably already have.

How it Works

AO3 already sends you an email every time a subscribed fic updates. The problem isn't the notification — it's that the email gets buried, and it tells you nothing about where you left off.

This solution uses Google Apps Script — free JavaScript that runs inside Google's ecosystem — to periodically scan your Gmail for AO3 update emails, parse the story title and chapter number out of the subject line, and write that data to a Google Sheet you can actually use.

Everything lives inside your existing Google account. No new services, no paid tiers, no custom domain.

The script runs on a schedule you set — every hour, every few hours, whatever suits you. Once it's running, it's fully automatic.

Want to try?

Step 1: Create your Google Sheet.

Create a new Google Sheet and name it whatever you like — "WIP Tracker" is fine. Add these column headers in row 1.

  • A: Story Title

  • B: Author

  • C: Latest Chapter

  • D: My Chapter

  • E: Last Updated

  • F: AO3 URL

  • G: Status

  • H: Notes

Latest Chapter (column C) is updated automatically by the script each time an update email arrives. My Chapter (column D) is what you fill in yourself when you finish reading. The gap between them shows you exactly how far behind you are on each fic.

Once your sheet is created, copy its ID from the URL — the long string between /d/ and /edit. You'll paste this into the script in Step 2.

Step 2: Add the Apps script.

From your Google Sheet, go to Extensions → Apps Script. This opens the script editor in a new tab. Delete any placeholder code you see and paste in the full script from the section below.

  1. In your Sheet, click ExtensionsApps Script

  2. Delete the default function myFunction() {} placeholder

  3. Paste in the full script from the script section below

  4. On line 3, replace YOUR_SHEET_ID_HERE with your actual Sheet ID from Step 1

  5. If your sheet tab isn't named "Sheet1", update line 4 to match your tab name

  6. Click Save (Ctrl/Cmd + S)

  7. Click Run to test it once manually — Google will ask for permissions on the first run

Note: Google shows an "unverified app" warning for personal scripts. This is expected — it means Google hasn't reviewed the code, not that something is wrong. Click Advanced → Go to [script name] (unsafe) to proceed. You're authorizing your own script to read your own Gmail and write to your own Sheet.

Step 3: Set up the trigger.

Right now the script only runs when you click Run manually. A time-based trigger makes it automatic.

  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 Choose which function to run to syncAO3Updates

  4. Set Select event source to Time-driven

  5. Set Select type of time based trigger to Hour timer

  6. Set Select hour interval to Every hour (or every 4 hours — your call)

  7. Click Save

Done. The script now runs on schedule and updates your Sheet whenever new AO3 emails arrive. You don't need to do anything else.

Step 4: Track your reading progress.

Column C (Latest Chapter) updates automatically. Column D (My Chapter) is yours — type in the chapter you're up to when you finish reading.

To make it easy to see at a glance where you're behind, add conditional formatting to highlight those rows:

  1. Select column C (Latest Chapter), rows 2 onwards

  2. Go to Format → Conditional formatting

  3. Set the range to C2:C1000

  4. Under Format rules, choose Custom formula is

  5. Enter: =C2>D2

  6. Pick a highlight colour — amber or soft yellow works well

  7. Click Done

Any fic where the latest chapter is ahead of yours will now be highlighted automatically. Filter column G (Status) to hide completed or on-hold fics and you have a clean, working reading dashboard.

The Full Script

Paste this entire block into the Apps Script editor. Replace YOUR_SHEET_ID_HERE on line 3 with your Sheet ID from Step 1.

/** WIP Tracker — syncs AO3 update emails to Google Sheets Setup: replace SHEET_ID with your own (found in your Sheet's URL) */ const SHEET_ID = 'YOUR_SHEET_ID_HERE'; const SHEET_NAME = 'Sheet1'; // change if your tab has a different name const PROCESSED_LABEL = 'AO3-Processed'; function syncAO3Updates() { const sheet = SpreadsheetApp .openById(SHEET_ID) .getSheetByName(SHEET_NAME); // Search for AO3 update emails not yet processed const query = 'from:[email protected] subject:"posted Chapter" -label:AO3-Processed'; const threads = GmailApp.search(query); if (threads.length === 0) return; // nothing new, exit // Get or create the processed label let label = GmailApp.getUserLabelByName(PROCESSED_LABEL); if (!label) label = GmailApp.createLabel(PROCESSED_LABEL); // Load existing sheet data for matching const data = sheet.getDataRange().getValues(); threads.forEach(function(thread) { thread.getMessages().forEach(function(message) { const subject = message.getSubject(); // AO3 subject format: [AO3] AuthorName posted Chapter X of Story Title const titleMatch = subject.match(/posted Chapter \d+ of (.+)/); const chapterMatch = subject.match(/Chapter (\d+) of/); const authorMatch = subject.match(/\[AO3\] (.+?) posted/); if (!titleMatch || !chapterMatch) return; // skip unparseable emails const title = titleMatch[1].trim(); const chapter = parseInt(chapterMatch[1]); const author = authorMatch ? authorMatch[1].trim() : ''; const timestamp = message.getDate(); // Find existing row (case-insensitive title match) let rowIndex = -1; for (let i = 1; i < data.length; i++) { if (data[i][0] && data[i][0].toString().toLowerCase() === title.toLowerCase()) { rowIndex = i + 1; // Sheets rows are 1-indexed break; } } if (rowIndex > 0) { // Update Latest Chapter and Last Updated — leave everything else alone sheet.getRange(rowIndex, 3).setValue(chapter); // C: Latest Chapter sheet.getRange(rowIndex, 5).setValue(timestamp); // E: Last Updated } else { // New fic — add a row (fill in AO3 URL yourself) sheet.appendRow([ title, // A: Story Title author, // B: Author chapter, // C: Latest Chapter '', // D: My Chapter — fill this in as you read timestamp, // E: Last Updated '', // F: AO3 URL — paste this in yourself 'Reading', // G: Status '' // H: Notes ]); } }); // Mark thread so it's never processed again thread.addLabel(label); }); }

What the script will and won't touch: On existing rows, the script updates only Latest Chapter(C) andLast Updated(E). It will never overwrite your My Chapter, AO3 URL, Status, or Notes. Those columns are entirely yours.

Known Gotchas

The permissions screen looks alarming, but it's fine.

Google flags any unreviewed personal script with a warning. Click Advanced → Go to [script name] (unsafe) to proceed. You're granting your own script access to your own data. Nothing is being shared externally.

First run will import all your old AO3 emails.

On the first run, the script will process every AO3 update email in your inbox — not just new ones. This is actually useful for kickstarting your tracker, but if you have years of emails it'll create a lot of rows at once. To limit this, temporarily add a date filter to the Gmail query on line 11, like after:2024/01/01, for the first run only. Remove it afterwards.

Title matching is exact (case-insensitive).

The script matches titles with a case-insensitive comparison. If you've manually typed a fic title into your Sheet with a typo or slightly different wording, the script will create a duplicate row instead of updating the existing one. Let the script create rows and only edit other columns, and you won't have this problem.

This only catches update emails, not first subscriptions.

When you first subscribe to a fic, AO3 doesn't send an email. The script will automatically add a fic to your Sheet the first time it updates after you subscribe. If you want to pre-populate fics before they update, add them manually.

Chapter number only — not chapter title.

AO3's subject line includes the chapter number but not the title. Chapter titles are in the email body, but body parsing is less reliable so we've kept the script focused on the subject line only.