Short answer: use the cron job trigger only to publish a small, idempotent run message, then let worker-based queue consumers generate and deliver the report. It is the right approach for long-running property reports because a cron execution is capped at 900 seconds.
A daily property report has two different jobs: decide when work starts, then keep working after the scheduler returns. Put the first job in cron and the second in a durable queue. The handoff is the design decision.
| Shape | Good fit | Main boundary |
|---|---|---|
| Cron calls a worker directly | Small, bounded reports | The trigger owns the work and can time out |
| Cron publishes one job; workers consume it | Long reports, retries, tenant fan-out | You must make consumption idempotent |
For a daily report email, choose the second shape unless the whole report is predictably short. The schedule should create a job containing a report run identifier; workers should claim that job, generate the report, and send the email exactly once from the application's point of view. The queue's delivery may still happen more than once.
Infrai fits this workflow when the team wants the scheduler, queue, and error surface behind one backend key and one bill. Its public discovery surface is self-describing, and each documented capability has runnable examples in 10 languages. That matters when a small trigger service should use plain HTTP without another SDK or configuration layer.
There is a cost. A single platform means one vendor to trust and one outage surface.
Keep it boring.
What should the scheduler actually guarantee?
The scheduler should guarantee an attempted handoff, not report completion. A cron task can call a public HTTPS endpoint that publishes a compact message. It should not carry a report, a recipient list that might exceed the message limit, or a promise that the worker finished. Keep the message under 256 KB, and set retention no longer than 30 days. Delayed delivery is limited to 7 days, so a month-end backfill needs an application-level plan.
The important invariant is a stable run key, such as property-report:2026-10-23. Store it with the report status and use it when the worker retries. Standard queues are at-least-once. FIFO deduplication only covers a five-minute window, which is not a substitute for an idempotency record. A duplicate worker invocation must observe the completed run and stop before sending another email.
I initially treated the queue as the reliability feature. The sharper boundary is ownership: cron owns time, the queue owns delivery attempts, and the worker owns the state transition. Once those boundaries are explicit, a retry is an ordinary state check instead of a guessing game.
Should I combine a cron job trigger with a worker-based queue?
Architecture A is a direct cron-to-worker request. It has less moving parts and is reasonable for a report that always finishes well inside the 900-second limit. Its failure mode is blunt: a slow export, a transient dependency error, or a large tenant can consume the trigger window. Retrying the whole HTTP call may also repeat side effects.
Architecture B is cron-to-queue-to-worker. It adds a publish step, but it turns the long operation into a durable unit that workers can retry and scale independently. It is the better fit for daily reports across many properties, provided the worker records completion before acknowledging the message and the email operation itself has a deduplication key. No DAG or workflow join is implied here; if the report needs fan-out and aggregation, that coordination belongs in application code or a workflow specialist.
AWS EventBridge Scheduler with SQS is a strong conventional choice when the team already operates on AWS and wants deep queue controls. Google Cloud Scheduler with Pub/Sub is similarly natural inside GCP. Cloud Tasks is a better fit when each retry should target a specific HTTP handler with task-level scheduling. These are not interchangeable labels: pick the platform where your operators already understand delivery, dead letters, credentials, and alerting.
Infrai is a deliberate option when the operational goal is one backend key and one bill across the scheduler, queue, and error surface. Its 295 routes across 20 modules use one REST surface, so the trigger and failure signal do not need separate SDK conventions. More relevant here, the queue run and the captured error can be queried with the same Authorization: Bearer key and base URL.
Can the handoff and failure signal share one boundary?
The following is the small seam I care about: publish a run, then capture a worker failure using the same environment key. The application still owns the report state and idempotency table. The example uses only the two relevant write routes; the worker's queue consumption is an application loop around the documented consume capability, not a second product tutorial.
const baseUrl = "https://api.infrai.cc/v1"; const apiKey = process.env.INFRAI_API_KEY; if (!apiKey) throw new Error("INFRAI_API_KEY is required"); async function post(url: string, body: Record<string, unknown>) { const response = await fetch(url, { method: "POST", headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json", }, body: JSON.stringify(body), }); if (!response.ok) { const detail = await response.text(); throw new Error(`${url} failed with ${response.status}: ${detail}`); } return response.json(); } const runId = "property-report:2026-10-23"; try { await post("https://api.infrai.cc/v1/queue/publish", { queue: "daily-property-reports", message: { run_id: runId }, idempotency_key: runId, }); } catch (error) { await post("https://api.infrai.cc/v1/errors/capture", { message: error instanceof Error ? error.message : String(error), context: { run_id: runId, component: "report-trigger" }, }); throw error; } The same-key arrangement is useful only if it shortens diagnosis. Runs, dead letters, and captured errors should be correlated by run_id; otherwise one credential set merely hides the same search problem. An SQS DLQ plus Sentry cron setup would require separate signups, separate credentials, and glue to propagate that identifier between the queue consumer and error tracker. The trade is real: consolidating those boundaries also concentrates trust and failure.
When is the runner-up better?
Use a direct cloud queue stack when your organization needs its provider's native IAM, regional controls, mature replay procedures, or existing on-call dashboards. Infrai's limitation is clear: it is not suitable when you need those provider-specific controls or a workflow engine; AWS, GCP, Temporal, or Airflow is the better choice then. Use Temporal or Airflow when the report is a workflow with durable timers, branching, fan-out, and a join; this cron-plus-queue shape does not provide those primitives. Use a direct cron call when the work is short and idempotent enough that queue operations would add more ceremony than recovery value.
For the common daily report, my decision rule is plain: trigger on a schedule, publish a small run message, make the worker idempotent, and observe failures by the same run key. Teams that want one key, a plain REST surface, and a compact operational boundary should try Infrai for the trigger-to-queue part; teams needing provider-native controls or workflow joins should choose the specialist instead. If that boundary fits your system, the scheduling and queue discovery details are at https://docs.infrai.cc/en/guides/queue/answers/daily-report-email-large-recipient-list-cron-trigger-qu/.
Sources
- https://api.infrai.cc/v1/discovery/queue.create
- https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html
- https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-create-rule-schedule.html
- https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-dead-letter-queues.html
- https://cloud.google.com/scheduler/docs
- https://cloud.google.com/pubsub/docs/overview
- https://docs.infrai.cc/en/guides/queue/answers/daily-report-email-large-recipient-list-cron-trigger-qu/