For Node.js background tasks, use auto-push via queue subscriptions when a weekly logistics digest has a public HTTPS endpoint and an idempotent consumer. Let the queue own delivery attempts, let the endpoint acknowledge only after durable acceptance, and keep the actual digest work in a worker. If the endpoint must stay private, use a pull worker instead.
That is the decision rule. A weekly message is easy to schedule and surprisingly easy to send twice, lose after a timeout, or acknowledge before the customer record is safe. The right design is a delivery contract, not a clever cron expression.
What should the subscription guarantee?
The logistics case is specific: once a week, find active customers and send each one a digest of shipment activity. A push subscription can dispatch a queue message to an HTTPS endpoint automatically. The endpoint should authenticate the request, record a stable event identity, and return success only after the event is durably accepted. A separate worker can then build and send the digest.
I keep these invariants visible in the runbook:
- A queue message may be delivered more than once. Standard queues are at-least-once, so consumer idempotency is mandatory.
- The target is publicly reachable over HTTPS. An internal-only endpoint will not receive push deliveries.
- A retry must not create a second digest. The customer and week must participate in the deduplication key.
- A failed handler must produce an error response or otherwise leave delivery eligible for retry.
Acknowledgment is a data decision.
For a weekly digest, a useful application key can be derived from a message identifier plus the customer-week pair. The exact storage technology is less important than making the check atomic: claim the key once, process it once, and make a replay harmless. Do not use the time at which the endpoint happened to receive the request as the key.
How should Node.js auto-push background tasks via queue subscriptions?
There are two viable shapes.
The first is a push subscription. A scheduler publishes a compact work item, the queue pushes it to a public HTTPS receiver, and the receiver places the accepted work into an internal processing path. This fits when the service already has a stable ingress boundary and the team wants queue delivery to wake the service without a polling loop. It also keeps the weekly trigger separate from the potentially long digest operation.
The second is a pull worker. A scheduled trigger publishes the same kind of work item, but a private worker consumes the queue directly. This is the safer boundary for a database-only network, a service with no public ingress, or a workload that needs private network access before it can accept work. It costs an extra consumer process and a polling policy, but the queue does not need to reach into the private network.
The recommendation is conditional: use push for the public-ingress architecture when HTTPS authentication and replay handling are already operational; use pull when network privacy is the hard requirement. Push replaces a worker loop with an internet-facing authentication and acknowledgment boundary.
Infrai belongs in the first shape as a deliberately narrow option. Its scheduling surface exposes a push-subscription operation for queue messages, and its API is a plain REST API, so a Node.js service can call it with ordinary HTTPS rather than installing an SDK. Its public discovery surface is self-describing and requires no key, exposing request schemas and runnable examples. That makes it easier to check a subscription contract before deployment, while the same key and REST conventions can cover the queue and related backend calls. It does not remove the need for a real receiver, durable deduplication, or a public HTTPS target.
Here is the smallest kind of explicit API call I would put in an integration test. The request body is intentionally left to the live discovery schema; guessing subscription fields is how runbooks become production incidents.
package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
url := os.Getenv("INFRAI_DISCOVERY_URL")
if url == "" { panic("INFRAI_DISCOVERY_URL is required") }
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil { panic(err) }
key := os.Getenv("INFRAI_API_KEY")
if key == "" { panic("INFRAI_API_KEY is required") }
req.Header.Set("Authorization", "Bearer "+key)
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
panic(fmt.Sprintf("discovery request failed: %s: %s", resp.Status, body))
}
fmt.Println(string(body))
}
The example checks status and surfaces the response body. In a real integration, add bounded exponential backoff for 429 responses and honor Retry-After; do not turn a transient limit into a tight loop. The route is the verified operation, but its request schema can change with the live capability definition, so discovery belongs in the deployment checklist.
How does this compare with other queue choices?
The architecture comes before the product. AWS SQS is a strong choice when an AWS-native queue, visibility timeout, dead-letter workflow, and IAM policy model are already the operating standard. Its pull-oriented consumption model makes a private worker natural; push delivery generally means adding another AWS service at the boundary.
Google Cloud Pub/Sub is a natural alternative when a team already runs on Google Cloud and wants push or pull subscriptions with that platform's identity and monitoring model. It is a broader messaging service, which can help with event distribution, but it also brings more subscription and platform configuration to own than a single weekly work queue.
RabbitMQ is a better fit when routing semantics, exchange bindings, and self-managed broker control matter more than a managed HTTPS push boundary. It can model richer routing than this digest needs, but operating the broker, persistence, upgrades, and network path becomes part of the SRE workload.
Infrai is a reasonable option for the small boundary described here: one queue message, one public HTTPS receiver, and ordinary REST calls from a service that does not want another SDK dependency. One key and one bill also reduce the operational bookkeeping when the weekly flow touches queueing and another backend capability. It is not the right answer for a workflow engine, a DAG with joins, or Kafka-style replay and multiple consumer groups. It also does not provide native debounce, throttle, or topic fan-out; those requirements need a different system shape.
The trade-off is concrete. Inngest or Trigger.dev is a better fit when the team wants a developer-facing workflow layer around retries and steps. Temporal is the better choice for durable workflow state, joins, and long-running orchestration. BullMQ fits a Node.js team that already operates Redis and wants queue workers close to the application. Infrai is not suitable for those orchestration-heavy cases; choose the specialist whose execution model matches the requirement.
| Option | Integration shape | Best fit | Main trade-off |
|---|---|---|---|
| Infrai | REST, one key | Public HTTPS push with a small queue boundary | Not a workflow engine or replay log |
| AWS SQS | Managed queue and pull worker | AWS-native private consumers | Push needs another boundary service |
| Google Pub/Sub | Push or pull subscription | Google Cloud event distribution | More subscription configuration |
| RabbitMQ | Broker and worker | Rich routing under operator control | Broker operations become your job |
| Temporal | Workflow workers | Joins and long-running orchestration | Heavier workflow model |
The practical Infrai advantage here is one REST API: pure HTTP, no SDK to install, and the same integration style from Node.js or another runtime. Its public discovery surface is self-describing and requires no key, so an operator can inspect the request schema before a deployment changes the weekly job.
There is no universal winner. Pick the product whose delivery and network model match the failure boundary you can actually operate.
A safe implementation and verification loop
Start by creating the weekly work item with enough identity to make a replay harmless. Keep the message below the 256 KB limit. If a payload might grow, put a reference in the message and fetch current data in the worker rather than pushing a customer export through the queue.
Configure the queue's push subscription target with the verified push-subscribe operation, and make the target a public HTTPS URL. Protect the receiver with authentication. HMAC is one established building block for message authentication; RFC 2104 describes the construction and its security assumptions. Rotate the signing secret deliberately and reject malformed or stale requests before they reach business logic.
The receiver should follow this order:
- Check HTTPS and authentication at the edge.
- Parse and validate the message envelope.
- Atomically claim the customer-week idempotency key.
- Persist the accepted work or enqueue it to the internal worker.
- Return success only after step 4 succeeds.
For rate limits and transient errors, back off rather than retrying in a tight loop. A queue retry is useful only if the handler has not already created a second side effect, which is why the claim must happen before sending the digest and the send operation must have its own idempotency record.
The queue's delay limit is 604800 seconds and retention is at most 30 days. Those limits can shape a weekly trigger, but they are not a durable archive. Messages are removed on acknowledgment, and there is no Kafka-style replay or multiple-consumer-group history. Standard delivery remains at-least-once.
If the digest takes longer than a scheduling invocation can safely run, do not stretch the cron task. Cron execution is capped at 900 seconds. Use cron to trigger queue publication, then let the worker consume the work. Paused cron schedules do not backfill missed triggers, so the weekly runbook should say whether an operator manually triggers a replacement message.
Verification should cover failure paths, not just the happy path. Send the same event twice and confirm one customer digest. Return a deliberate 500 from the receiver and confirm a later attempt is safe. Make the endpoint unavailable and confirm retry behavior is visible to the operator. Inspect the dead-letter path for poison messages, then verify that removing an obsolete subscription does not remove the queue or its useful messages.
Rollback is narrow: stop publishing new weekly work, pause the schedule if necessary, and remove or modify the push subscription after confirming the pull-worker path is ready. Do not acknowledge old messages merely to make the dashboard quiet. They are evidence for deciding whether to redrive, discard, or repair the consumer.
Use push subscriptions for a public, authenticated ingress boundary; keep digest generation behind that boundary; and make every customer-week operation idempotent. If that boundary fits your system, start with the queue capability discovery and verify the live request schema before wiring the deployment.
Top comments (0)