1. Work
  2. Automation and operations tooling

Safe follow-up assistant for unanswered emails

Finds sent emails still unanswered after a set number of days and sends polite follow-ups inside the original thread, with the user controlling every message and no thread offered twice.

Problem

Important emails that never get a reply are easy to lose track of. Following up by hand means searching the sent folder, checking every thread for a reply and writing near-identical nudges, with the risk of nudging the same person twice.

Goal

Find sent emails still unanswered after a set number of days and send polite follow-ups inside the original thread, with the user controlling every message and no thread offered for a follow-up twice.

My role

Sole author: design, mail API integration, thread analysis, persistence, the command-line experience and the safety features.

Solution

A command-line tool that signs in with the narrowest read and send permissions, scans the sent folder, decides per thread whether a follow-up is needed, lets the user choose threads, previews each message built from a template, asks for explicit confirmation, replies in the same thread and records it locally so the thread is never offered again. Dry-run is the default.

How it works

The first run uses an OAuth desktop flow and stores a token on the local machine.

A scan lists recent sent messages, deduplicates them by thread and skips any thread already in the local ledger.

For each remaining thread it fetches the conversation, sorts it by time and confirms that the first message was the user's own. The thread counts as answered if the latest message came from someone else; a thread whose last message is still the user's own counts as waiting.

Days waiting are compared with a configurable threshold, with an optional recipient or domain filter.

Selected threads get a filled template and a preview, and are sent only after a yes. The follow-up goes into the original conversation by sending with that thread's identifier and a matching reply subject.

Each outcome is written to a local table keyed one row per thread, and every scan checks that table before offering a thread again. Every action is logged, and history and settings commands support audit.

Technical challenges

Deciding "unanswered" from thread structure alone: sort by time, require that the first message is the user's own, and treat the thread as answered only if the latest message came from someone else.

Keeping the nudge inside the original conversation rather than starting a new one.

A safe default path: dry-run unless explicitly switched off, a preview and confirmation per message, read and send permissions only, and token and history kept on the machine.

Duplicate protection through a one-row-per-thread ledger checked before anything is offered.

Capabilities

  • OAuth sign-in with minimal permissions
  • Sent-folder scanning
  • Per-thread reply detection
  • Configurable wait period
  • Recipient and domain filtering
  • Interactive multi-select
  • Preview and per-message confirmation
  • In-thread replies
  • Dry-run mode
  • Duplicate prevention
  • Action log and history view

Technology

  • Node.js
  • Mail provider REST API with OAuth 2.0
  • SQLite
  • Interactive command-line UI toolkit
  • Environment-based configuration

Impact

A manual, error-prone routine became a guarded, auditable one where nothing is sent without a preview and a yes, and a thread already followed up is never offered again. No metrics are claimed.

Skills demonstrated

  • API integration and OAuth
  • Duplicate-safe automation design
  • Safe-by-default outbound tooling
  • Data modelling
  • Command-line UX
  • Logging and audit trails

Demo

Planned

A browser mock of the terminal against an invented mailbox, with fictional people and companies and subjects such as "Quote for spring catalogue": a scan finds four unanswered threads (one excluded because the other side replied, one because it was already followed up), then multi-select, a template preview, a confirmed mock send, the history view, and a "run again" button showing that the sent threads are no longer listed. The dry-run toggle is shown separately and never feeds the rerun.

Not built yet. A demo here uses only invented names and data.

All work