# How followup-cadence.mjs Calculates Optimal Timing for Application Follow-ups

> Discover how followup-cadence.mjs calculates optimal timing for application follow-ups using configurable business rules and pure functions to determine deterministic dates and urgency levels.

- Repository: [Santiago Fernández de Valderrama/career-ops](https://github.com/santifer/career-ops)
- Tags: how-to-guide
- Published: 2026-06-10

---

**The `followup-cadence.mjs` script calculates optimal timing by applying configurable business rules to each application's status, days since submission, and follow-up history, returning deterministic dates through pure functions like `computeNextFollowupDate()` and urgency levels via `computeUrgency()`.**

The `followup-cadence.mjs` module in the `santifer/career-ops` repository serves as the core engine for the follow-up cadence tracker. It determines exactly when you should contact employers next and flags how urgent those follow-ups are. By parsing markdown trackers and applying deterministic logic based on the `CADENCE` configuration, it removes guesswork from calculating optimal timing for application follow-ups.

## The Three-Stage Calculation Pipeline

The script processes data through a logical pipeline that transforms raw tracker files into actionable scheduling recommendations.

### Stage 1: Data Normalization and Parsing

First, the script ingests and standardizes input data. It reads [`data/applications.md`](https://github.com/santifer/career-ops/blob/main/data/applications.md) and [`data/follow-ups.md`](https://github.com/santifer/career-ops/blob/main/data/follow-ups.md), then converts raw status strings to canonical forms like `applied`, `responded`, and `interview`.

- **`parseTracker()`** (lines 85–101) extracts application dates, company names, and current statuses from the applications tracker.
- **`parseFollowups()`** (lines 105–127) processes the follow-up history log to determine `followupCount` and `lastFollowupDate`.
- **`normalizeStatus()`** (lines 59–63) maps various input strings to standardized state values for consistent downstream processing.

### Stage 2: Urgency and Date Computation

For each actionable entry, the script calculates three key metrics: **days since application** (`daysSinceApp`), **days since last follow-up** (`daysSinceLastFollowup`), and **number of previous follow-ups** (`followupCount`). These metrics feed into two pure functions:

- **`computeUrgency()`** (lines 55–73) returns one of four priority levels: `urgent`, `overdue`, `waiting`, or `cold`.
- **`computeNextFollowupDate()`** (lines 75–91) returns the specific calendar date for the optimal next contact, or `null` if the application has gone cold.

### Stage 3: Ranking and Output Generation

Finally, entries are sorted by urgency (from `urgent` to `cold`) according to the sorting logic (lines 60–63). The script generates either a JSON payload (lines 68–80) or a human-readable dashboard containing the suggested next date, days until that date, and the calculated urgency level. Users can filter results using flags like `--overdue-only` to focus on immediate priorities.

## Cadence Configuration and Business Rules

All timing decisions derive from the **`CADENCE`** constant defined at lines 32–40. This configuration object centralizes the business logic for calculating optimal timing:

```js
const CADENCE = {
  applied_first:        7,   // days after first application before first follow-up
  applied_subsequent:   7,   // days between successive follow-ups while "applied"
  applied_max_followups:2,   // stop after two follow-ups – entry becomes "cold"
  responded_initial:    1,   // urgent if response received within 1 day
  responded_subsequent: 3,   // overdue after 3 days without new follow-up
  interview_thankyou:   1,   // thank-you after interview should be sent next day
};

```

Runtime customization is available via CLI arguments. For example, `--applied-days <n>` (line 30) overrides `applied_first` to adjust the initial waiting period without modifying source code.

## Computing the Next Follow-Up Date

The `computeNextFollowupDate()` function implements status-specific logic to determine the optimal contact date. It uses **`addDays()`** (lines 79–83) to perform calendar calculations, adding specified day intervals to ISO-8601 dates.

### Applied Status Logic

For applications with status `applied`, the timing depends on follow-up history:

```js
if (status === 'applied') {
  if (followupCount >= CADENCE.applied_max_followups) return null;   // "cold"
  if (followupCount === 0) return addDays(parseDate(appDate), CADENCE.applied_first);
  // After the first follow-up
  return addDays(parseDate(lastFollowupDate), CADENCE.applied_subsequent);
}

```

If no follow-ups exist, the script schedules the first contact `applied_first` days after the application date. Subsequent follow-ups occur every `applied_subsequent` days. After reaching `applied_max_followups`, the function returns `null`, indicating the lead has gone cold.

### Responded Status Logic

When an employer responds, the urgency increases. The script calculates the next follow-up based on when the response occurred:

```js
if (status === 'responded') {
  if (lastFollowupDate) return addDays(parseDate(lastFollowupDate), CADENCE.responded_subsequent);
  return addDays(parseDate(appDate), CADENCE.responded_subsequent);
}

```

If the user has already followed up on the response, it schedules the next contact `responded_subsequent` days after that last follow-up. Otherwise, it calculates from the original application date.

### Interview Status Logic

For interview stages, the timing focuses on post-interview etiquette:

```js
if (status === 'interview') {
  return addDays(parseDate(appDate), CADENCE.interview_thankyou);
}

```

The function returns a date exactly `interview_thankyou` days after the interview, ensuring prompt thank-you communication.

## Urgency Classification Logic

The `computeUrgency()` function evaluates the same temporal inputs to surface priority levels. It categorizes each application to help users triage their job search:

- **Applied entries** become `overdue` when the first-follow-up window or subsequent window is exceeded, otherwise `waiting` or `cold`.
- **Responded entries** trigger `urgent` if the first response is fresh (less than `responded_initial` days old), and `overdue` after the `responded_subsequent` threshold passes.
- **Interview entries** show `overdue` once the thank-you window (`interview_thankyou`) has passed.

This classification drives the ordering in the final JSON output and the color-coded terminal dashboard (lines 102–124).

## Usage Examples

### Programmatic API

You can import the calculation functions directly to integrate optimal timing logic into custom workflows:

```js
import {
  computeNextFollowupDate,
  computeUrgency,
  parseDate,
} from './followup-cadence.mjs';

// Example: Application submitted on 2024-03-01, still "applied", no prior follow-up
const status = 'applied';
const appDate = '2024-03-01';
const lastFollowupDate = null;
const followupCount = 0;

// Determine the optimal next contact date
const next = computeNextFollowupDate(status, appDate, lastFollowupDate, followupCount);
console.log('Next follow-up should be on:', next);   // → 2024-03-08 (default 7 days)

// Determine urgency (assume today is 2024-03-09)
const today = new Date('2024-03-09');
const daysSinceApp = Math.floor((today - parseDate(appDate)) / (1000*60*60*24));
const urgency = computeUrgency(status, daysSinceApp, null, followupCount);
console.log('Urgency level:', urgency);             // → "overdue"

```

### Command-Line Execution

Run the full analysis from your terminal to process tracker files and generate reports:

```bash

# JSON output (default)

node followup-cadence.mjs > cadence.json

# Human-readable dashboard

node followup-cadence.mjs --summary

# Show only overdue items

node followup-cadence.mjs --overdue-only

```

The CLI reads [`data/applications.md`](https://github.com/santifer/career-ops/blob/main/data/applications.md) and [`data/follow-ups.md`](https://github.com/santifer/career-ops/blob/main/data/follow-ups.md), applies the cadence rules, and prints either structured JSON or a formatted table based on the output mode selected.

## Summary

- **`followup-cadence.mjs`** calculates optimal timing through a three-stage pipeline: data parsing, metric computation, and urgency-based ranking.
- **Timing logic** is driven by the `CADENCE` configuration object (lines 32–40), which defines intervals for first follow-ups, subsequent contacts, and maximum outreach attempts.
- **Pure functions** `computeNextFollowupDate()` and `computeUrgency()` return deterministic dates and priority levels based on application status, elapsed days, and follow-up count.
- **Status-specific rules** handle `applied`, `responded`, and `interview` stages differently, ensuring context-appropriate scheduling.
- **Flexible output** supports both JSON API consumption and human-readable dashboards via CLI flags.

## Frequently Asked Questions

### How does the script handle applications where I've already followed up multiple times?

The `computeNextFollowupDate()` function tracks `followupCount` and compares it against `CADENCE.applied_max_followups`. If you have reached the configured maximum (default is 2), the function returns `null` and the entry is classified as `cold`, indicating you should stop following up and move on to other opportunities.

### Can I customize the number of days before my first follow-up?

Yes. You can override the `applied_first` value at runtime using the `--applied-days <n>` CLI flag (line 30), or modify the `CADENCE` constant directly in the source code at lines 32–40. This allows you to adjust your approach from the default 7-day waiting period to match industry-specific expectations or personal preference.

### Why does the script calculate different intervals for "responded" versus "applied" statuses?

The `CADENCE` configuration recognizes that employer responses require faster reaction times. While `applied` status uses a 7-day cycle, `responded` status triggers an `urgent` classification within 1 day (`responded_initial`) and marks items `overdue` after 3 days (`responded_subsequent`). This reflects the higher priority of maintaining momentum when an employer shows active interest compared to cold applications.

### What file formats does the script expect for input data?

The script expects markdown tracker files located at [`data/applications.md`](https://github.com/santifer/career-ops/blob/main/data/applications.md) and [`data/follow-ups.md`](https://github.com/santifer/career-ops/blob/main/data/follow-ups.md). The `parseTracker()` function (lines 85–101) and `parseFollowups()` function (lines 105–127) parse these files to extract dates, company names, contact information, and status history, converting them into JavaScript objects for processing.