npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dead-check

v0.1.1

Published

A minimal dead man's switch for your app. If a user goes quiet for N days, someone they trust gets notified.

Readme

npm version License: MIT Built for Still Employed

dead-check

A minimal dead man's switch for your app.
If a user stops checking in, someone they trust gets notified.

Built for Still Employed — the app where people check in daily to confirm they haven't been replaced by AI. If they go quiet for 2 days, their emergency contact gets an email.

User checks in → streak continues → everyone's fine
User goes quiet → 2 days pass → their person gets an email

dead-check demo


Why this exists

The original "Are You OK?" apps (like the Japanese 死んでいませんか) proved one thing: sometimes people just need a quiet, low-stakes way to say "I'm still here."

dead-check is that primitive, extracted into a reusable module. Use it for:

  • Employment anxiety — daily check-in to confirm you're still employed (our use case)
  • Mental health — gentle "are you okay?" nudge to someone's support person
  • Elderly care — family gets notified if grandma doesn't check in
  • Habit tracking — accountability partner gets pinged if you skip
  • Solo travel — emergency contact notified if you go off-grid too long
  • Any app where "going quiet" means something important

How it works

┌─────────────┐     POST /checkin       ┌──────────────────┐
│    User     │ ──────────────────────▶ │  dead-check API  │
│  (daily)    │                         │                  │
└─────────────┘                         │  - updates       │
                                        │    lastCheckin   │
                                        │  - increments    │
┌─────────────┐   cron: check daily     │    streak        │
│  Scheduler  │ ──────────────────────▶ │                  │
└─────────────┘                         │  - finds users   │
                                        │    silent for    │
                                        │    N days        │
┌─────────────┐   sends email           │                  │
│  Emergency  │ ◀────────────────────── │  - notifies      │
│  Contact    │                         │    contact       │
└─────────────┘                         └──────────────────┘

Quick start

npm install dead-check
import { DeadCheck } from 'dead-check'

const dc = new DeadCheck({
  silenceDays: 2,                    // how many days of silence triggers notification
  notifyFn: async (user) => {        // bring your own email sender
    await sendEmail({
      to: user.emergencyEmail,
      subject: `Check on ${user.name}`,
      body: emailTemplate(user),
    })
  },
})

// In your check-in endpoint:
await dc.checkin(userId)

// In your daily cron job:
await dc.runCheck()                  // finds silent users, calls notifyFn for each

That's it. Two functions.


Full API

new DeadCheck(config)

interface DeadCheckConfig {
  silenceDays:  number                           // required. days before notifying
  notifyFn:     (user: SilentUser) => Promise<void>  // required. called once per silent user
  db?:          DeadCheckAdapter                 // optional. bring your own DB adapter
  onCheckin?:   (userId: string) => void         // optional. hook after check-in
  onNotify?:    (user: SilentUser) => void        // optional. hook after notification sent
}

dc.checkin(userId: string): Promise<CheckinResult>

interface CheckinResult {
  userId:    string
  streak:    number     // consecutive days checked in
  isNew:     boolean    // first ever check-in
  lastSeen:  Date
}

dc.runCheck(): Promise<CheckResult>

Run this daily via cron. Finds all users whose lastCheckin is older than silenceDays.

interface CheckResult {
  checked:   number   // total users scanned
  silent:    number   // users who triggered notification
  notified:  number   // notifications successfully sent
  errors:    number   // notifications that failed
}

dc.getUser(userId: string): Promise<UserStatus>

interface UserStatus {
  userId:        string
  streak:        number
  lastCheckin:   Date | null
  isSilent:      boolean
  daysSinceLast: number
}

Database adapters

dead-check ships with a Prisma adapter. Plug in your own schema:

// prisma/schema.prisma — add these fields to your User model:
model User {
  id                 String    @id @default(cuid())
  // ... your existing fields ...

  // dead-check fields:
  emergencyEmail     String?
  lastCheckin        DateTime?
  streak             Int       @default(0)
  notifiedAt         DateTime? // when we last sent the notification
}
import { PrismaAdapter } from 'dead-check/adapters/prisma'
import { prisma } from '@/lib/prisma'

const dc = new DeadCheck({
  silenceDays: 2,
  db: new PrismaAdapter(prisma),
  notifyFn: async (user) => { /* ... */ },
})

Custom adapter

Implement the DeadCheckAdapter interface to use any database:

interface DeadCheckAdapter {
  getUser(userId: string):             Promise<DCUser | null>
  updateCheckin(userId: string):       Promise<DCUser>
  getSilentUsers(since: Date):         Promise<DCUser[]>
  markNotified(userId: string):        Promise<void>
}

Email templates

dead-check ships with two templates out of the box. Use them, modify them, or write your own.

Default template (warm)

Subject: A quick check — [Name] hasn't checked in

Hey,

You're listed as [Name]'s emergency contact on Still Employed.

They haven't checked in for 2 days. This might mean nothing —
maybe they're on a trip, maybe they just forgot.

But they trusted you enough to put your name down. So we thought
you should know.

— dead-check

Minimal template

Subject: [Name] · 2 days since last check-in

[Name] hasn't checked in since [date].
You're their emergency contact. That's all we know.

Usage

import { templates } from 'dead-check/templates'

const dc = new DeadCheck({
  silenceDays: 2,
  notifyFn: async (user) => {
    await resend.emails.send({
      to:      user.emergencyEmail,
      from:    '[email protected]',
      subject: templates.warm.subject(user),
      html:    templates.warm.html(user),
    })
  },
})

Next.js example

// app/api/checkin/route.ts
import { dc } from '@/lib/dead-check'

export async function POST(req: Request) {
  const session = await getServerSession()
  if (!session) return Response.json({ error: 'unauthorized' }, { status: 401 })

  const result = await dc.checkin(session.user.id)
  return Response.json(result)
}
// app/api/cron/dead-check/route.ts
import { dc } from '@/lib/dead-check'

export async function GET(req: Request) {
  // Protect with a secret
  const auth = req.headers.get('authorization')
  if (auth !== `Bearer ${process.env.CRON_SECRET}`) {
    return Response.json({ error: 'unauthorized' }, { status: 401 })
  }

  const result = await dc.runCheck()
  return Response.json(result)
}
// lib/dead-check.ts — singleton
import { DeadCheck, PrismaAdapter } from 'dead-check'
import { prisma } from './prisma'
import { resend } from './resend'
import { templates } from 'dead-check/templates'

export const dc = new DeadCheck({
  silenceDays: 2,
  db: new PrismaAdapter(prisma),
  notifyFn: async (user) => {
    await resend.emails.send({
      to:      user.emergencyEmail!,
      from:    '[email protected]',
      subject: templates.warm.subject(user),
      html:    templates.warm.html(user),
    })
  },
})

See examples/nextjs for a complete working example.


Vercel Cron setup

// vercel.json
{
  "crons": [{
    "path": "/api/cron/dead-check",
    "schedule": "0 9 * * *"
  }]
}
# .env
CRON_SECRET=your-secret-here

Configuration reference

| Option | Type | Default | Description | |--------|------|---------|-------------| | silenceDays | number | — | Required. Days of silence before notifying | | notifyFn | function | — | Required. Called for each silent user | | db | adapter | in-memory | Database adapter | | onCheckin | function | — | Hook after successful check-in | | onNotify | function | — | Hook after notification sent | | notifyOnce | boolean | true | Don't re-notify if already notified recently | | renotifyAfterDays | number | 7 | Days before re-notifying if still silent | | timezone | string | UTC | Timezone for silence calculation |


Philosophy

dead-check does one thing: notice when someone goes quiet, and tell someone who cares.

It doesn't track location. It doesn't read messages. It doesn't make assumptions about why someone went quiet. It just notices, and nudges.

The logic is under 200 lines. The rest is your app.


Demo

Try dead-check interactively with the built-in demo app:

cd demo
npm install
npm run dev

Open http://localhost:3000 — check in, simulate silence, run the check, and see the full notification email output.


Contributing

PRs welcome, especially for:

  • New database adapters (Drizzle, Mongoose, Supabase)
  • Email provider examples (SendGrid, Postmark, AWS SES)
  • New templates
  • silenceHours option for sub-day precision

License

MIT


Built for Still Employed — check in daily, confirm you're still here.
If you stop, someone you trust finds out.