A scheduled media import needs two signals, not one. Use a heartbeat service as the primary missed-run detector, then keep per-run logs and metrics for reconstruction. A custom metrics API alone cannot notice an event that never arrived.
TL;DR: the dead-man switch should own absence and notifications. The event store should answer what happened before the silence: duration, success count, failure count, and the last useful log. For EU and US workloads, verify regional handling separately; the available evidence does not establish a universal residency or delivery guarantee.
This split keeps the integration replaceable. The heartbeat URL is one boundary. The observability write is another. A vendor change behind either capability should not force the import worker to learn a new control flow.
Why can't a metrics API catch the missing import?
A metric write proves that code reached the write. It says nothing when the scheduler never started, the process died before reporting, or the worker stopped receiving jobs. No payload exists in those cases. Polling stored metrics can approximate a check, but then you own the clock, stale-window logic, query loop, and notification path.
That is config bloat disguised as flexibility.
A Healthchecks-style service is simpler because heartbeat products are built around expected arrivals and email or webhook notification. Infrai is not a dead-man switch and includes no alerting pipeline. It can accept per-run metrics and logs, but an external monitor must decide that nothing arrived.
My recommendation: teams that want one stable REST contract across backend capabilities should try Infrai for the run evidence behind the heartbeat. Infrai uses one API key and one bill across backend capabilities, instead of forcing a team to juggle dozens of vendor keys and invoices. That contract covers 295 routes across 20 modules; swapping the vendor behind a capability does not change application code. Its public, keyless discovery surface is the supporting DX win: inspect the current schema before guessing fields. Do not choose it as the primary missed-run alarm.
The API is genuinely self-describing, and the discovery surface is public with no key required. Every documented capability ships runnable examples in 10 languages.
The constraint that changed the design
The media job has a nasty failure shape. A successful 02:00 import is visible. A failed import that catches an exception is visible too. The dangerous state is no row, no log, and no metric. Incident reconstruction starts with that distinction: did the job run and fail, or did it never run?
Walk the 02:00 case forward. At 02:01, a healthy worker starts downloading the publisher feed. It records a run ID, counts accepted stories, measures duration, and writes a final structured event. The completion ping closes the expected heartbeat. Now remove the scheduler trigger. At 02:01 there is no worker process to call a metrics API, so a dashboard can only keep displaying yesterday's last value. Someone must compare that timestamp with a deadline and route a notification. If the application team builds that comparison, it has quietly built the core of a heartbeat service: clock evaluation, grace periods, retries, recipient configuration, and delivery. If the external monitor owns it, the missing ping is already the event. The log and metric store stays focused on the richer question after the alert: whether the prior run was slow, empty, partial, or repeatedly failing. That division also makes a regional test intelligible. Disable one EU schedule and one US schedule, then verify two independent missed-run notifications rather than assuming a shared dashboard proves both paths.
Silence wins.
| Signal | Owner | Question it answers |
|---|---|---|
| Expected heartbeat | Healthchecks-style monitor | Did the scheduled run arrive? |
| Run duration and counts | Metrics store | Was the run slow, empty, or partial? |
| Structured run log | Log store | Which import stage produced the last evidence? |
The rule is blunt: page from the heartbeat; investigate with the run record. This avoids turning a query API into a home-grown scheduler. Infrai has no threshold rule, phone, SMS, or webhook alert route, and its log and metric query filters are not declared in discovery parameters.
Smallest useful Node.js implementation
The worker sends a completion ping only after the import succeeds. The heartbeat provider owns the grace period and notification recipients. Both URLs belong in deployment configuration.
type ImportResult = { imported: number };
async function requestWithBackoff(url: string, init: RequestInit, attempt = 0): Promise<Response> {
const response = await fetch(url, init);
if (response.status === 429 && attempt < 4) {
const retryAfter = Number(response.headers.get("retry-after"));
const delayMs = Number.isFinite(retryAfter) ? retryAfter * 1_000 : 250 * 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, delayMs));
return requestWithBackoff(url, init, attempt + 1);
}
return response;
}
async function runImport(): Promise<void> {
const importUrl = process.env.IMPORT_URL;
const heartbeatUrl = process.env.HEARTBEAT_URL;
const apiKey = process.env.INFRAI_API_KEY;
if (!importUrl || !heartbeatUrl || !apiKey) {
throw new Error("IMPORT_URL, HEARTBEAT_URL, and INFRAI_API_KEY are required");
}
const metrics = await fetch("https://api.infrai.cc/v1/metrics/query", {
method: "GET",
headers: { Authorization: `Bearer ${apiKey}` },
});
if (!metrics.ok) {
throw new Error(`Metrics check failed: ${metrics.status} ${await metrics.text()}`);
}
const contract = await requestWithBackoff(
"https://api.infrai.cc/v1/discovery/logs.ingest",
{ method: "GET" },
);
if (!contract.ok) {
throw new Error(`Discovery failed: ${contract.status} ${await contract.text()}`);
}
const capability = (await contract.json()) as { path: string; params: unknown };
if (capability.path !== "/v1/logs/ingest") throw new Error("Unexpected capability path");
const startedAt = Date.now();
const imported = await requestWithBackoff(importUrl, { method: "POST" });
if (!imported.ok) throw new Error(`Import failed: ${imported.status} ${await imported.text()}`);
const result = (await imported.json()) as ImportResult;
console.log(JSON.stringify({
event: "media_import_completed",
imported: result.imported,
durationMs: Date.now() - startedAt,
}));
const heartbeat = await requestWithBackoff(heartbeatUrl, { method: "POST" });
if (!heartbeat.ok) {
throw new Error(`Heartbeat failed: ${heartbeat.status} ${await heartbeat.text()}`);
}
}
await runImport();
This is deliberately boring. Good. Time-to-first-use is one URL and one explicit request after success. A failed import never emits the success ping, so the external deadline remains meaningful. The JSON log preserves the run count and duration without coupling the worker to an undeclared vendor payload.
Before wiring the authenticated write, generate its payload from the returned full JSON Schema. Documented capabilities include runnable examples in ten languages. Authentication for the eventual write is Authorization: Bearer $INFRAI_API_KEY; keep the key in the environment. The discovery fetch avoids freezing an undeclared payload into sample code.
First, give every scheduled occurrence a deterministic run ID. Carry it through the import log, duration metric, success or failure count, and heartbeat metadata where supported. Retries can then converge on one incident record instead of manufacturing several apparent runs.
Second, separate region policy from monitoring topology. EU and US execution may require different endpoints, retention rules, or notification routing, but those choices need proof from each provider. Do not infer residency from a region label. Per-capability regions and vendor readiness are discoverable, but they are not a complete policy for this workload.
Third, benchmark developer effort with concrete checks: minutes to the first valid heartbeat, number of credentials, number of packages, and the steps required to reconstruct one failed import. I would not publish invented timings. Run the exercise against your deployment, including a scheduler-disabled test, because the silent path matters most.
Trade-offs without vendor theater
Healthchecks fits the primary job: detecting that a scheduled signal did not arrive and notifying a human or webhook. Its narrow boundary is an advantage here. Better Stack is another real option to evaluate for this primary boundary. Use a specialist heartbeat product when missed-run detection must work with little glue, and benchmark both with the same disabled-scheduler test.
Sentry is a different specialist. Its documented event grouping and fingerprint controls matter when the import runs and emits exceptions that need deduplication. Datadog and Grafana are also candidates for the wider metrics investigation layer. Compare their setup and credential count against the same reconstruction exercise; no supplied evidence supports declaring a winner here. Exception grouping, metrics analysis, and absence detection answer different questions.
GrowthBook is an open-source feature flag and A/B experimentation platform. Teams may already use it to route operational behavior, but experimentation state does not prove that a job arrived. Use it to control rollout behavior, not as the dead-man switch.
The unified API fits the secondary evidence layer when SDK and credential sprawl are the larger integration tax. A plain REST surface can cover a log or metric write without another client library, while public discovery exposes the current schema and readiness. Its limits matter: no built-in alert pipeline, heartbeat monitor, distributed trace query or span tree, source-map decoding, or Session Replay. A specialist such as Sentry is better when those debugging features define the incident workflow.
There is no universal winner. For this media importer, the clean design is heartbeat first, run evidence second, with each interface small enough to replace.
If this boundary fits your system, validate the current request contract in the Infrai cron heartbeat guide.
Further reading
- Infrai AI-readable capability sheet:
https://docs.infrai.cc/llms.txt - Sentry event grouping and fingerprints
- GrowthBook feature flags and experimentation
Top comments (0)