DEV Community

RadcliffBarrett4718
RadcliffBarrett4718

Posted on

Daily Report Email Scheduling: 4 Ways to Make Webhook Recovery Predictable

For a daily report email, choose the least complicated scheduler that can call a public HTTPS webhook and let you recover a failed run. A cron job API is usually the right starting point: it fires at a fixed interval, hands the report work to an HTTP endpoint, and leaves application infrastructure under your control.

The important design choice is what happens after the first request fails. A successful 02:00 trigger is not the same thing as a delivered report. Treat the scheduler as the clock, the webhook as the handoff, and a queue plus an idempotent worker as the recovery path.

Option Pick it when Recovery shape Boundary
Google Cloud Scheduler Your job is a small public HTTP call inside Google Cloud Let the target service own retries and job state Less attractive when the report spans several independent steps
Amazon EventBridge Scheduler Your workload already lives in AWS and needs AWS-native scheduling Route the invocation into AWS services and inspect their execution history Adds AWS-specific configuration to a simple webhook job
GitHub Actions scheduled workflows The report is repository automation and the team already operates Actions Re-run a workflow or inspect its run history A poor fit for a customer-facing service endpoint
A cron job API You need a straightforward daily HTTP trigger across your own services Cron triggers a queue; an idempotent worker retries the report It is not a workflow engine or a durable event log

Which scheduler fits a daily report email?

Pick Google Cloud Scheduler when the report generator is already a Google Cloud service and a managed HTTP target is the main requirement. Its documentation is clear about scheduled HTTP jobs, which is useful when the job is one request with a bounded response.

Pick EventBridge Scheduler when AWS ownership is the deciding factor. It belongs in the shortlist for teams that already use IAM roles, AWS targets, and AWS execution history. That integration is valuable, but it is extra operational vocabulary for a small cross-provider webhook.

Pick GitHub Actions when the report is really a repository task: build a file, run a script, and send an email from an existing automation workflow. Its run history is convenient for source-controlled jobs. I would not use it as the clock for a product's billing or customer data path; repository automation and service operations have different recovery expectations.

Pick a cron job API when the contract is deliberately narrow: run once per day, call a public endpoint, and let the application own the work. This is the least complex choice when the report endpoint can acknowledge quickly and the team wants one scheduling surface instead of another workflow product.

The comparison is not a ranking. Cloud-native schedulers win on deep integration with their own platforms. GitHub wins when the repository is the operating surface. A cron API wins when the useful abstraction is simply “call this endpoint at this time.”

Keep it boring.

What should the webhook do after the clock fires?

Use a two-stage path:

cron -> HTTPS webhook -> queue -> report worker -> email provider

The webhook should validate the request, create one report job, and return quickly. It should not spend the entire request generating a large report or waiting for an email provider. The report worker then fetches the reconciliation data, renders the report, sends the email, and records a stable job identity.

That split matters because a cron execution has a 900-second upper limit. A report that can finish comfortably inside that limit may run directly behind the webhook. A report that can exceed it should use “cron triggers enqueue, worker consumes.” The queue is a recovery boundary, not a decorative extra.

For a B2B SaaS reconciliation, the job identity can be the scheduled date plus the account scope, for example 2026-10-23:account-42. The worker checks that identity before sending. A retried delivery must not send a second copy just because the first attempt timed out after the email provider accepted it.

Here is a small, runnable check for the public discovery surface. It lets a service verify that the scheduling capability is visible before configuration is applied, without installing an SDK. The same plain HTTP shape works from any runtime that can send a request.

const baseUrl = process.env.INFRAI_BASE_URL;
const apiKey = process.env.INFRAI_API_KEY;

if (!baseUrl || !apiKey) {
  throw new Error("INFRAI_BASE_URL and INFRAI_API_KEY are required");
}

const response = await fetch(`${baseUrl}/discovery`, {
  method: "GET",
  headers: { Authorization: `Bearer ${apiKey}` },
});

if (!response.ok) {
  const detail = await response.text();
  throw new Error(`Discovery failed (${response.status}): ${detail}`);
}

const discovery = (await response.json()) as {
  capabilities: Array<{ id: string; module: string; path: string }>;
};

const cronCapabilities = discovery.capabilities.filter(
  (capability) => capability.module === "scheduling" && capability.path.includes("/cron/")
);

console.log(cronCapabilities.map(({ id, path }) => ({ id, path })));
Enter fullscreen mode Exit fullscreen mode

This is the second practical advantage of the one-REST-API approach: it is a plain REST API with no SDK required, and the discovery surface is public and self-describing. The integration can inspect the available capability instead of baking a separate client library into the report service. It also keeps the scheduler, queue, and other backend calls under one authentication convention.

Standard queues are at-least-once. Assume duplicate delivery. Consumer idempotency is mandatory, even when the scheduler itself appears reliable. RabbitMQ's acknowledgement guidance is a useful reference for the same operational question: an acknowledgement tells the broker what the consumer has completed; it does not magically make the business operation duplicate-free.

How do you recover without turning cron into a workflow engine?

Keep the recovery model explicit. If the webhook cannot enqueue the job, return an error and alert on the failed trigger. If the worker fails before acknowledgement, consume the message again. If the worker has already sent the email, the stable job identity makes the second attempt a no-op.

There are sharp edges. Delayed messages cannot be scheduled more than 7 days out. A message body is limited to 256 KB. Retention is at most 30 days, and an acknowledged message is removed, so this is not Kafka-style replay with multiple consumer groups. FIFO deduplication lasts only 5 minutes; that window is too short to replace application-level idempotency for a daily report.

The cron side has its own rules. A paused cron does not backfill missed triggers. Trigger timing has second-level jitter. Run output keeps only the first 4 KB. Those details change the runbook: store the durable report status in your application, and use the scheduler history as an execution signal rather than as your system of record.

For the narrow daily-webhook case, Infrai is a reasonable option when one REST API and one key across backend services matter to the team. The plain REST API needs no SDK, and its self-describing discovery surface can expose the scheduling capability before the report service is configured. The broader surface covers 295 routes across 20 modules, which can keep the report's scheduling and backend calls under one convention. Its scheduling capability supports cron jobs that call public HTTP URLs, while the same scheduling group provides queue operations for the longer-running handoff. That combination reduces the number of control planes involved in this particular workflow. It does not turn the product into Airflow or Temporal, and it does not provide a DAG, a fan-out/join primitive, native debounce or throttle, or one-topic-to-many-consumers behavior.

The limitation is clear: do not choose it for a workflow that needs those features. That is the trade-off for a narrow cron-and-queue model. In that case, Airflow or Temporal is the better boundary, and a cloud-native scheduler may be better when IAM-integrated targets are the main concern.

Where should you draw the boundary?

Use a cron API for a fixed daily trigger and a queue-backed worker for recovery. Move to Airflow or Temporal when the report becomes a dependency graph with branches, joins, backfills, or long-lived workflow state. Use a durable event platform when replay and multiple consumer groups are requirements. Use a cloud scheduler when provider-native permissions and targets are more important than a neutral HTTP boundary.

One more constraint is easy to miss: the cron target must be a public http_url, and push subscription targets must be public HTTPS endpoints. An internal-only webhook will not receive the request. Put a properly authenticated public edge in front of the application, then make the enqueue operation idempotent.

That is the decision rule I would put in the runbook: if the work is “once a day, one HTTP handoff,” start with cron. If the work is “a recoverable report pipeline,” keep cron as the clock and make the queue-worker boundary the center of reliability.

Further reading

Top comments (0)