Domain and TLS expiry watcher

A daily check that ranks domains and certificates by expiry, then keeps one rolling issue current.

April 22, 2026 · 2 min read · 362 words
.md

The cheapest outage to prevent is the one where a domain or certificate quietly lapses. This job runs daily, checks registration and TLS expiry for everything you watch, and nags you with a single rolling issue before anything goes dark.

check-expiry.ts
const ALERT_DAYS = Number(process.env.ALERT_DAYS ?? '60');
 
async function expiryFor(domain: string) {
  // RDAP first (registry server, then rdap.org), whois as fallback.
  for (const url of rdapUrls(domain)) {
    const data = await fetchJson(url); // retries 429/5xx, treats 404 as definitive
    const event = data?.events?.find((e) => e.eventAction === 'expiration');
    if (event) return { source: 'rdap', expiry: new Date(event.eventDate) };
  }
  const fromWhois = await whoisExpiry(domain); // matches common expiry lines
  return fromWhois ?? { source: 'unknown', expiry: null };
}
 
const rows = await mapPool(watched, 4, async (d) => {
  const { expiry, source } = await expiryFor(d);
  const daysLeft = expiry ? Math.floor((+expiry - Date.now()) / 86_400_000) : null;
  return { domain: d, expiry, source, daysLeft };
});
rows.sort((a, b) => (a.daysLeft ?? Infinity) - (b.daysLeft ?? Infinity));

Degrade, never fail the run#

Every lookup is wrapped so one unresolvable domain returns unknown instead of crashing the job. Network calls retry transient 429/5xx with backoff and treat 404 as final, and the whole fan-out runs through a bounded pool of four — not an unbounded Promise.all that hammers registries. The report always prints, even when a few rows can't be determined.

One rolling issue, not a daily inbox flood#

When anything is inside the alert window the job opens or updates a single issue titled "Domains expiring soon", and auto-closes it when everything is healthy again. You get notified on real change, not a green email every morning — the surest way to teach people to ignore the alert.

Was this useful?

React if it helped; comment if you have a concrete question, correction, or field note.

-

Discussion (0)

Practical notes, bug stories, and disagreement with receipts are welcome.

No comments yet. A useful first comment is usually a field note: what failed, what held, or what you would check before shipping this idea.
Start the discussion
Markdown is supported. Keep it concrete and useful.