Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

Build permit expiration alerts as a repeatable database scan, not as one timer per permit: configure NestJS scheduling once, select permits whose reminder window is due, atomically record each reminder, and deliver notifications through a retryable worker or outbox. This design makes repeated scans and process restarts manageable while keeping permit-specific date rules explicit.

Choose the expiration rule before scheduling

A timestamp is only useful if it represents the rule the permit authority actually uses. Some permits expire at a precise instant; others expire on a calendar date under a jurisdiction’s local rules. Do not assume that midnight UTC is the local expiration boundary.

  • For an instant-based deadline, store the instant and compare it against the current time.
  • For a date-based deadline, retain the legal date and the applicable jurisdiction or IANA time zone, then calculate the reminder instant from the governing policy.
  • Confirm the deadline and interpretation with the permit authority. Framework and database documentation cannot establish legal expiration rules.

In PostgreSQL, timestamp with time zone input is converted to UTC for storage; output is rendered in the session time zone, and the original input zone is not retained. Store the zone or jurisdiction separately when the original local meaning matters. Time-zone conventions can change, including through political changes to daylight-saving rules. See PostgreSQL’s date/time type documentation.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Configure one NestJS scheduler

Install and configure NestJS’s @nestjs/schedule package using the instructions for your project’s NestJS version. Import ScheduleModule.forRoot() in the root application module only. NestJS documents that this call initializes the scheduler and registers declarative cron jobs, timeouts, and intervals; calling it in multiple modules can register the handlers repeatedly. See NestJS Task Scheduling.

A provider can own the recurring scan. For example, a daily run at 02:15 in UTC can be declared with @Cron('0 15 2 * * *', { timeZone: 'UTC' }). NestJS cron expressions can include a seconds field; choose a cadence and zone that match the permit policy and operational needs rather than copying this example blindly.

Use waitForCompletion: true if one scheduler process must not start another invocation while its prior callback is still running. That option skips overlapping invocations in that scheduler. It is not a lock across application replicas, and it does not create a durable record of missed runs.

Build a repeatable scan and claim each reminder

Keep the cron handler short: determine which reminders are due and persist work to be delivered. Let a separate worker perform slower email, SMS, or other provider calls.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  1. Find candidates. Select active permits whose expiration falls within the configured reminder window, scoped by tenant or jurisdiction as appropriate. Use a stable, well-defined window so a delayed or repeated scan can still find due work.
  2. Claim idempotently. In the same database operation or transaction, insert a reminder/outbox record with a unique key such as permit ID, reminder offset, and destination. A uniqueness constraint makes rerunning the scan safe: the same logical reminder is not enqueued twice.
  3. Persist delivery state. Track the claim, queued or pending state, attempts, timestamps, and final outcome. The record should let operators determine which reminder was due and what happened to it.
  4. Deliver asynchronously. A worker reads pending records, sends the message, and updates status. Retry transient failures according to an explicit policy; make retries safe if the provider call succeeds but the process fails before recording success.
  5. Monitor delayed work. Alert on scan failures, growing pending work, repeated delivery errors, and unusually old reminders. Define how far back a recovery scan should look after downtime.

A scheduler callback alone cannot guarantee exactly-once delivery. Database deduplication prevents duplicate logical reminder creation; delivery idempotency and provider behavior determine whether recipients might receive duplicates after ambiguous failures.

Choose how scanning and delivery run

Approach Best fit Trade-off
In-process @Cron() scan A simple recurring sweep with modest operational requirements Instance lifecycle, missed-run recovery, and coordination among replicas remain application responsibilities.
Durable workflow schedule Missed-run and overlap behavior must be persisted and explicit It is a separate framework facility and operational model; verify compatibility with the project’s NestJS version.
Queue-backed notification worker Delivery retries or independent scaling of notification work are needed Requires queue infrastructure and idempotent delivery handling.
Database claim/outbox Durable work tracking close to permit data is desirable Requires transaction design, cleanup, and monitoring.

For a simple deployment, an in-process cron can be a practical starting point, provided scans are idempotent and due reminders remain recoverable in durable application data. If workers run on multiple replicas, use a deliberate claim or coordination protocol rather than assuming only one process will execute the cron. NestJS’s Durable Workflows documentation describes cron schedules, intervals, recurrence rules, time zones, missed-run policies, and overlap policies. Its missed-run default is skip; once starts the latest missed occurrence, while all starts missed occurrences (up to 100 latest) and requires overlap: 'allow'. This is not the same mechanism as basic @nestjs/schedule, and availability depends on project compatibility.

NestJS queues can move delivery into separate consumers, which helps isolate and scale work independently of the database scan. The cited queue documentation is for NestJS v9; confirm current package compatibility before adopting version-specific details. See NestJS Queues v9.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Index and claim records for the real query

Index the columns used by the actual candidate scan, commonly status or tenant scope together with expiration time. A partial index for active permits can be useful when the schema and query support it, but its predicate must be stable. PostgreSQL partial-index predicates can reference only columns of the indexed table, and index expressions must be immutable; a predicate that rolls with “expires before now” is not suitable. Compare expiration to the current time in the query instead. See PostgreSQL CREATE INDEX.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For concurrent workers, PostgreSQL’s FOR UPDATE SKIP LOCKED can support a deliberate queue-like row-claiming protocol, allowing consumers not to wait on rows another transaction has locked. It returns an intentionally inconsistent view, so it is not a general-purpose read strategy. Persist the claim in a transaction, and retain unique reminder keys and safe delivery handling; row locking alone does not provide deduplication. See PostgreSQL SELECT.

Validate failure and calendar cases

  • Repeated scan: Run the same scan again and verify the unique reminder key prevents a second pending record.
  • Restart or missed run: Stop the application across a scheduled time, restart it, and verify the recovery window finds reminders still due.
  • Multiple replicas: Run workers concurrently and confirm claims are transactional, with no lost or duplicated work.
  • Delivery ambiguity: Simulate a provider success followed by a process failure before status update; ensure retry behavior is understood and safe.
  • Time-zone boundary: Test permits around local midnight and daylight-saving transitions using the intended jurisdiction policy.
  • Expired and inactive permits: Confirm that status changes, renewals, cancellations, and already-expired permits alter eligibility as required.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.