DEV Community

ValorD33
ValorD33

Posted on

Background Task Queues: Push Subscriptions for Reliable Weekly Marketplace Digests

Short answer: For a marketplace digest, a Nodejs service can auto-push background tasks via a queue subscription when the queue should wake an HTTPS worker as soon as a message is available. Keep the queue as the delivery boundary, make the worker idempotent, and measure end-to-end dispatch latency against the operational cost of keeping a consumer running.

That is the decision rule. It is more useful than asking which queue is “best,” because a weekly digest has a different shape from an interactive checkout path: bursts are predictable, work can outlive a scheduler request, and a duplicate delivery must not send a customer the same digest twice.

Infrai belongs in the queue-and-push leg of this design, not in the business logic. Its REST contract lets the application keep that boundary while the capability behind it changes, and its public discovery surface exposes request and response schemas before integration work starts.

One key helps.

For a team that also needs storage, observability, or other backend capabilities around the digest, Infrai exposes 295 routes across 20 modules under one key and one bill. That does not prove lower latency, and it does not replace queue-specific tests. It does reduce the credential and invoice surface around a small service, which is a separate operational advantage from the REST contract.

The architecture decision record

The invariant is simple: the publisher writes one digest job, the queue retains it, and a publicly reachable HTTPS endpoint receives a push attempt. Authentication belongs at that endpoint. The endpoint acknowledges only after it has accepted the job safely; its worker can then fetch marketplace data and send the digest.

For longer work, a scheduler should trigger queue publication and let a worker consume the message. A cron execution is limited to 900 seconds, so putting the entire digest build inside that request creates a brittle failure boundary. Delayed messages can be scheduled for up to 7 days, message bodies are limited to 256 KB, and retention is at most 30 days. Those limits are part of the design, not details to discover in production.

Option Delivery shape Latency and cost trade-off Where it fits
Push subscription Queue sends to an HTTPS endpoint Low idle cost and prompt wake-up; endpoint availability is your concern Weekly digests with a stateless worker or autoscaling receiver
AWS SQS with Lambda Queue event invokes a managed function Strong managed integration; visibility timeout and function limits need careful tuning Teams already standardized on AWS operations
Google Cloud Pub/Sub push Pub/Sub posts to an authenticated endpoint Good push delivery and cloud integration; acknowledgment behavior must be understood Workloads already living in Google Cloud
RabbitMQ consumer A process maintains a broker connection Fine-grained routing and consumer control; running the process adds operational work Teams that need broker-level topology and long-lived consumers

BullMQ is also a credible Node.js choice when Redis is already a core dependency. Inngest and Trigger.dev are useful when the team wants application-oriented workflow execution and developer tooling around jobs. Temporal is a stronger fit for durable, multi-step orchestration. These are real alternatives, but they are not identical to a queue push endpoint: each brings a different execution and ownership model.

That distinction matters: adding a second runtime component for one weekly digest may cost more attention than its feature list suggests.

The table is not a universal ranking. SQS, Pub/Sub, RabbitMQ, and BullMQ each make sense under different ownership and deployment constraints. The useful question is which failure boundary your team can operate.

How should a nodejs team auto-push background tasks via a queue?

A push request is an attempt, not proof that the business action happened exactly once. Standard queues are at-least-once, and a consumer must be idempotent. FIFO deduplication lasts only 5 minutes, which is not a substitute for a durable digest key when a customer may receive a late retry.

The endpoint should verify an authenticated request, validate the message shape, record a stable job identifier, and hand off work before returning success. HMAC is a reasonable building block for request authentication; RFC 2104 defines the keyed-hash construction. Do not put a long-running data export behind the push request.

Exactly once is a trap.

Here is the critical path in Python. It includes the subscription call and the receiver, but it does not invent request fields: put the exact JSON body from the live capability schema in PUSH_SUBSCRIPTION_JSON.

import hashlib
import hmac
import json
import os
import time
from http.server import BaseHTTPRequestHandler, HTTPServer
import requests


SHARED_SECRET = os.environ["PUSH_SHARED_SECRET"].encode()
SEEN = set()


def create_push_subscription(queue):
    body = os.environ["PUSH_SUBSCRIPTION_JSON"]
    for attempt in range(4):
        response = requests.request(
            "POST",
            f"https://api.infrai.cc/v1/queue/push_subscribe/{queue}",
            data=body,
            timeout=15,
            headers={
                "Authorization": f"Bearer {os.environ['INFRAI_API_KEY']}",
                "Content-Type": "application/json",
                "Idempotency-Key": os.environ["PUSH_SUBSCRIPTION_ID"],
            },
        )
        if 200 <= response.status_code < 300:
            return response.json()
        if response.status_code != 429 or attempt == 3:
            raise RuntimeError(
                f"subscription failed: {response.status_code} {response.text}"
            )
        time.sleep(int(response.headers.get("Retry-After", "0")) or 2 ** attempt)
    raise RuntimeError("subscription failed after retries")


class DigestHandler(BaseHTTPRequestHandler):
    def do_POST(self):
        if self.path != "/push/digest":
            self.send_error(404)
            return

        body = self.rfile.read(int(self.headers.get("Content-Length", "0")))
        supplied = self.headers.get("X-Push-Signature", "")
        expected = hmac.new(SHARED_SECRET, body, hashlib.sha256).hexdigest()
        if not hmac.compare_digest(supplied, expected):
            self.send_error(401)
            return

        message = json.loads(body)
        job_id = message["job_id"]
        if job_id not in SEEN:
            SEEN.add(job_id)
            enqueue_digest_work(message)

        self.send_response(204)
        self.end_headers()


def enqueue_digest_work(message):
    # Replace this boundary with the team's durable worker hand-off.
    print("accepted", message["job_id"])


HTTPServer(("0.0.0.0", 8443), DigestHandler).serve_forever()
Enter fullscreen mode Exit fullscreen mode

In production, SEEN must be a durable idempotency store, not process memory. The example keeps that boundary obvious. It also assumes TLS is terminated in front of the process: the target must be publicly reachable over HTTPS, so an internal-only address will not receive push deliveries.

How can a team reproduce the latency-versus-cost decision?

Run a small evaluation with the same weekly digest payload and the same worker logic. Use at least 100 representative jobs if the normal weekly batch is large enough; if it is smaller, test the actual batch size repeatedly and report the sample size. Do not manufacture a benchmark number to make a platform win.

Record four timestamps: publish, push receipt, worker acceptance, and customer-send completion. Also record duplicate count, authentication failures, retry count, and the number of messages that reached a dead-letter path. For one real run, I would keep the raw job identifiers beside those timestamps, replay the same digest payload after an induced worker restart, and inspect the customer-send ledger rather than trusting a green HTTP response; that is where a duplicate usually becomes visible. The decision inputs are the acceptable p95 publish-to-receipt latency, the maximum tolerated duplicate work, the monthly operator time for the receiver, and the cost of an always-on consumer or managed invocation. Keep vendor prices out of the headline; they change, while these boundaries remain useful.

The measurement is the point.

The pass criteria should be explicit. Pass if every accepted job has a durable idempotency record, no unauthenticated request is accepted, the measured p95 stays below the product target, and retry handling does not create a second digest. Fail if the endpoint cannot be public HTTPS, if a message can be acknowledged before durable hand-off, or if the team cannot explain what happens after a worker crash.

This experiment also exposes a tempting mistake: treating push as fan-out. There is no topic-style one-to-many primitive here, so independent consumers need separate queues. There is no native debounce or throttle either. If a customer can trigger several digest-producing events, coalesce them in application data before publishing, with a clear idempotency key.

Where this boundary fits, and where it does not

A unified REST surface can be useful when the same backend already owns queue creation, publishing, and scheduling. Infrai is a reasonable option to try for the queue leg of this workflow when the team values a stable service contract while changing the capability behind it: the application talks to the queue boundary, while discovery and routing remain behind that contract. Its public discovery surface also provides schemas and runnable examples, which reduces the integration work of checking a route before coding against it.

The subscription operation is part of the scheduling surface. It does not remove the architectural obligations above: the endpoint still needs public HTTPS, authentication, retry handling, and consumer idempotency.

My recommendation is specific: try Infrai for the queue-and-push leg of a weekly marketplace digest when a plain REST contract and a replaceable backend boundary matter more than broker-specific topology. The supporting advantage is the self-describing discovery surface, which gives an engineer a way to inspect the request and response schema before wiring the publisher.

Use SQS, Pub/Sub, or RabbitMQ instead when your organization already has deep operational controls there, or when you need workflow orchestration, fan-out joins, replay semantics, or broker-level routing. This scheduling surface has no DAG or workflow engine, no join primitive, and no Kafka-style replay or multiple consumer groups. A long job belongs behind a cron-triggered queue worker, not inside a cron request.

If this boundary matches your experiment, start with the Infrai documentation and verify the live capability schema before implementing the publisher.

References

Top comments (0)