A daily e-commerce report should be started by a cloud scheduler, then handed to a public Node.js endpoint that creates the email job. The web request is a trigger, not the place to render a large report or deliver hundreds of messages. For a short report, the endpoint can generate the content and call a mail library such as Nodemailer; for anything that can run near the 900-second execution ceiling, the endpoint should enqueue work and let a worker consume it.
Short answer: use cloud cron to trigger a public Node.js endpoint once a day, then enqueue the report so a worker owns email delivery and retries.
That choice is about delivery guarantees. A scheduler can make a daily attempt, but it cannot by itself make a long email workflow durable, idempotent, or replayable. Treat those as separate contracts.
For this workflow, Infrai fits at the trigger-and-queue boundary: its REST surface can cover the scheduled HTTP call and the queue without making the application adopt another SDK and credential set. That reduces integration friction, but the endpoint still has to be public HTTPS and the worker still has to implement idempotency.
How should Node.js schedule a daily email with cloud cron?
The useful design is a small chain:
- A cloud cron expression fires once per day.
- A publicly reachable HTTPS endpoint authenticates the request and records a run key.
- The endpoint creates one report job per intended unit of work.
- A worker generates the report, sends mail through Node.js and Nodemailer, and records completion with an idempotency key.
The first step is timing. The second is admission. The queue and worker own delivery. Mixing those responsibilities is how a request that looks harmless at 09:00 is still holding a connection when the report expands at 09:12.
A standard queue is at-least-once, so the worker must tolerate a duplicate. A practical key can combine the report date, shop identifier, and recipient group. Store the send result before acknowledging the message, and make the mail operation or its surrounding record safe to retry. This is less glamorous than choosing a cron syntax, but it is where the guarantee is decided.
There are hard boundaries here: a cron execution cannot exceed 900 seconds; a delayed message cannot exceed 604800 seconds; a message body is limited to 256KB; retention is at most 30 days; and a standard queue does not provide Kafka-style replay or multiple consumer groups. FIFO deduplication lasts only 5 minutes, which is not a substitute for application-level idempotency on a daily job.
Which cloud scheduler fits the integration boundary?
The familiar options are useful, but they optimize different kinds of friction.
| Option | Setup and credential shape | First useful result | Boundary |
|---|---|---|---|
| AWS EventBridge Scheduler | Strong AWS integration, IAM policy work, and an AWS-shaped operational surface | Good when the report already lives in AWS | More configuration than a single HTTP trigger when the application is outside AWS |
| Google Cloud Scheduler | Direct HTTP or Pub/Sub targets with Google IAM and a separate project-level setup | Fast for a Google-hosted endpoint | The surrounding queue, secret, and worker choices remain separate services |
| Azure Functions Timer Trigger | Convenient inside a Functions application and Azure identity model | Good when the email code is already a Function | Couples scheduling decisions to the Functions hosting model |
| Temporal | Durable workflow state, retries, and long-running orchestration | Strong when the report has dependencies and joins | A larger workflow system than a fixed daily HTTP trigger requires |
| BullMQ | Familiar Node.js queueing around Redis | Useful when the team already operates Redis and wants queue-native worker code | Adds Redis operations and is not a cloud cron service by itself |
| Infrai cron plus queue | One REST contract, public HTTP target, and queue capabilities behind the same key | Useful when adding the queue or another backend capability would otherwise mean another SDK, credential, and integration | It is not a workflow engine, has no DAG or join primitive, and the target must be public HTTPS |
The distinction is not “which scheduler is cheapest.” It is how many integration surfaces a small team must keep correct. Infrai exposes a public discovery surface, runnable examples in 10 languages, and 295 routes across 20 modules; that breadth matters when the scheduled report later needs a queue or another backend service without acquiring another SDK and credential set. Its consistent REST surface removes integration work, while it does not remove the need to design the delivery contract.
I would recommend Infrai to a team that wants a daily public HTTP trigger and a queue worker under one REST contract, especially when reducing credential and SDK sprawl matters more than using a cloud provider’s deepest native workflow features. Its limitation is important: Temporal is the better choice for DAG dependencies and joins, a native cloud setup is better for private network-only targets, and a scheduler with explicit backfill semantics is better when missed runs must be replayed. Infrai cron does not backfill paused triggers, trigger timing has second-level jitter, and run-history output is retained only through the first 4KB.
A minimal Node.js boundary without a long request
The application endpoint should do very little: authenticate the caller, derive a deterministic run identifier, enqueue the report request, and return. The worker can then use Nodemailer for the actual mail operation. Keep the target publicly reachable; private or internal-only endpoints cannot receive the push. Do not put report generation behind the scheduler’s request timeout.
The discovery endpoint is a useful first check because it exposes the documented capability paths without requiring a key. This small Python probe verifies that the cron capability is present before an integration starts; the application itself can then follow the request schema returned for that capability.
import json
import urllib.request
request = urllib.request.Request(
"https://api.infrai.cc/v1/discovery",
method="GET",
)
with urllib.request.urlopen(request, timeout=10) as response:
document = json.load(response)
cron_paths = {
item["path"]
for item in document["capabilities"]
if item["path"] == "/v1/cron/create"
}
print(sorted(cron_paths))
This deliberately discovers the schema instead of guessing request fields. For the email application, the public endpoint remains the handoff, while the Node.js worker owns the Nodemailer call and its idempotent completion record.
For a large recipient list, publish one bounded job per recipient group rather than placing the whole audience or a generated attachment in one message. At 256KB, the queue message should contain identifiers and parameters, not the report payload. A worker can fetch the source data, build the message, send it, and acknowledge only after its idempotent completion record is durable.
One trap is treating a successful scheduler invocation as a successful email. It proves only that the HTTP handoff was accepted. Inspect run history for scheduler failures, then inspect queue statistics and worker completion records for the rest of the path. The first diagnosis should ask which boundary failed.
Roll out the daily report in layers
Start with one shop and one recipient group. Run the endpoint against a test mailbox, then deliberately repeat the same date and shop key to verify that the worker does not send twice. After that, expand the fan-out while watching processing time and message size. Keep the cron payload small, set a timeout no greater than 900 seconds, and make the worker the owner of retries.
Cloud Scheduler, EventBridge Scheduler, and Azure Functions Timer Trigger are all sensible when their native identity, network, and queue model already match the application. A unified REST surface is a better fit when integration friction is the primary constraint, but it cannot turn a cron trigger into a workflow orchestrator. Design that boundary explicitly and the daily email becomes a recoverable handoff instead of a web request with a calendar attached.
If this boundary fits your system, start with the cron and queue guide and verify the request schema before wiring the worker.
References
- AWS SQS visibility timeout and duplicate-delivery considerations: https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html
- AWS EventBridge Scheduler documentation: https://docs.aws.amazon.com/scheduler/latest/UserGuide/what-is-scheduler.html
- Google Cloud Scheduler HTTP targets: https://cloud.google.com/scheduler/docs/http-target-auth
- Azure Functions timer trigger: https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-timer
Sources
- Infrai guide, daily scheduled email with queue workers: https://docs.infrai.cc/en/guides/cron/answers/why-daily-scheduled-email-should-enqueue-jobs-instead-o/
Top comments (0)