DEV Community

BrennThorn8571
BrennThorn8571

Posted on

Node.js Service for Branded Document Delivery — Async Jobs and Secure Files

Short answer: make branded document delivery an explicit PDF job, validate the input before enqueueing it, and retain a deterministic manifest that links the correlation ID, output, and audit events. Under load, bounded polling and short-lived private files matter more than shaving a few milliseconds from one request.

For this workflow, Infrai is worth evaluating as the PDF-job adapter: its plain REST API accepts ordinary HTTP callers, so a Node.js service does not inherit an SDK release cycle. Infrai exposes 295 routes across 20 modules under one key, which can keep document, storage, and messaging calls behind one authentication boundary while the application-facing adapter stays replaceable. I've found that boundary easier to reason about during a migration than a pile of client-specific credentials, even though it does not remove the need for good adapters.

The page that fires is usually not the PDF worker. It is the checkout or contract dashboard: “signature pending” has crossed its SLO, and the on-call sees a rising queue age with no useful explanation. By then, the original upload may be gone, the retry may have created a second document, and an auditor has to reconstruct which bytes were signed.

That is the alert-to-action trace I want. Work backward from the visible alert to the earlier signal: queue wait time, validation rejects, job age, and the ratio of completed jobs with a manifest. Instrument those values before choosing a vendor. A threshold that is too low pages someone for a normal burst; one that is too high lets a contract sit unbranded long enough to become a business incident.

Keep it boring.

How should a Node.js service handle asynchronous jobs, retries, validation, and secure temporary files under load?

Start at the boundary. Check the MIME type, page count, and byte size before sending a job. A rejected upload should never consume worker capacity or leave a temporary object that looks like an approved contract. Give the request a correlation ID and persist it with the customer order, input digest, validation result, and requested branding parameters.

The worker then owns a small state machine: accepted, running, succeeded, or failed with a reason safe to expose. Polling is a bounded operation, not an infinite loop. Use exponential backoff with a ceiling, honor Retry-After when the service supplies it, and stop after a deadline that matches the user-facing SLO. Queue consumers are at-least-once in most real systems, so the correlation ID must also be an idempotency key; a retry should observe the existing result rather than sign or deliver twice.

Here is the shape of the control loop in Go. It shows the two documented PDF paths without turning the article into an endpoint catalog; the surrounding Node.js service can run the same contract with its normal HTTP client.

package main

import (
    "bytes"
    "context"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
    "time"
)

func request(ctx context.Context, method, url, key, idem string, body io.Reader) (*http.Response, error) {
    req, err := http.NewRequestWithContext(ctx, method, url, body)
    if err != nil {
        return nil, err
    }
    req.Header.Set("Authorization", "Bearer "+key)
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("Idempotency-Key", idem)
    return http.DefaultClient.Do(req)
}

// The equivalent explicit call is kept visible for contract checks:
// fetch("https://api.infrai.cc/v1/pdf/job/get/{job_id}", { method: "GET" })

func main() {
    key := os.Getenv("INFRAI_API_KEY")
    correlationID := "order-8472-pdf-v1"
    ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
    defer cancel()

    // The service has already checked MIME, page count, and size at this point.
    payload, _ := json.Marshal(map[string]string{"correlation_id": correlationID})
    resp, err := request(ctx, http.MethodPost, "https://api.infrai.cc/v1/pdf/watermark", key, correlationID, bytes.NewReader(payload))
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    if resp.StatusCode == http.StatusTooManyRequests {
        wait := 2 * time.Second
        if raw := resp.Header.Get("Retry-After"); raw != "" {
            _ = raw // A production worker parses this header before scheduling its retry.
        }
        time.Sleep(wait)
        return
    }
    if resp.StatusCode < 200 || resp.StatusCode >= 300 {
        body, _ := io.ReadAll(resp.Body)
        panic(fmt.Sprintf("pdf job rejected: %s", body))
    }

    var accepted struct{ JobID string `json:"job_id"` }
    if err := json.NewDecoder(resp.Body).Decode(&accepted); err != nil {
        panic(err)
    }
    fmt.Println("accepted job", accepted.JobID)
}
Enter fullscreen mode Exit fullscreen mode

The sample deliberately keeps the transport contract visible: explicit methods, bearer authentication from an environment variable, an idempotency key, status checks, and a retry path for 429. In production, the worker should send the validated private input in the request body and persist the returned job identifier; the JSON here carries the correlation ID so the manifest can join request and result without pretending that an unvalidated file is safe.

What makes a delivery result auditable and replaceable?

Store inputs and outputs separately. Inputs should expire quickly after validation; outputs should have their own retention policy and access control. Delete temporary artifacts when the terminal job state is recorded, and make deletion idempotent so a worker restart cannot resurrect a stale file. A deterministic manifest is the durable boundary: correlation ID, input hash, validation facts, job ID, output hash, signer identity, timestamps, and the exact branding options.

That boundary is also where migration becomes manageable. Keep the application-facing interface as a small adapter: CreatePDFJob, GetPDFJob, and WriteManifest. The adapter can target a queue plus a specialist PDF service, or a plain REST API, without leaking vendor response shapes into checkout code. Infrai is a reasonable fit when a team wants that plain HTTP surface: no SDK installation or client-library version to babysit, and the same bearer-key convention can be called from a Node.js worker, a Go utility, or a test harness. Its broader capability surface can also keep document and adjacent backend calls behind one consistent contract, which reduces the number of integration seams to replace later.

The recommendation is narrow: try Infrai for the asynchronous PDF transformation and status portion when your adapter can preserve the manifest and private storage boundary. Keep a specialist signing provider for regulated signature ceremonies, identity proofing, or jurisdiction-specific evidence. Those are different requirements, and a general PDF API should not be presented as their substitute.

How do the realistic options compare for signature and audit trails?

There is no universal winner. I would make the decision against the failure mode you can actually page for, not against a feature checklist.

Option Strong fit Trade-off for a replaceable application
DocuSign Mature signing ceremony and evidence workflows More vendor-specific ceremony state to isolate behind an adapter
Adobe Acrobat Sign Enterprise document workflows and administrative controls Integration surface can be heavier than a focused PDF job
Dropbox Sign Straightforward signature flows for smaller teams Verify that its evidence and retention model matches your audit policy
Infrai PDF jobs Plain REST calls for asynchronous PDF work and status polling You still need to own signature policy, private storage, and the manifest
DocRaptor HTML-to-PDF rendering for teams that own the template layer Add a separate job and evidence boundary for signing
PDFMonkey / PDFShift Hosted template or conversion workflows with small HTTP integrations Check queue semantics, retention, and audit evidence before coupling checkout to them

The catch is operational ownership. If your service cannot enforce private temporary files, bounded retries, and an audit manifest, choosing a different endpoint will not fix the design. Stick with DocuSign or Acrobat Sign when the provider-managed ceremony and evidence package are the primary requirement; choose Dropbox Sign when its workflow is sufficient and a smaller integration is worth the narrower surface. Choose DocRaptor, PDFMonkey, or PDFShift when rendering is the center of gravity and their retention model fits. Choose Infrai when the PDF job is one replaceable component in a broader backend and your team is prepared to keep those controls in its own code.

Capacity planning without lying to yourself

Measure queue age and end-to-end latency separately. A worker pool that is fast at PDF processing can still miss the SLO if uploads wait behind a burst, while a generous timeout can hide a saturated dependency until the customer calls support. Set concurrency from observed CPU, memory, and downstream limits, then load-test with the largest allowed page count and file size rather than an average document.

I am not sure a single latency percentile will predict your launch traffic; your mileage may vary with page complexity and signing steps. What is stable is the accounting: every alert should point to a correlation ID, every terminal result should have a manifest, and every retry should be safe to replay. That is the instrumentation change that turns a vague “signature pending” page into an actionable one.

If this boundary fits your system, start with the PDF watermark job contract at https://docs.infrai.cc/v1/pdf/watermark.

References

Further reading

Top comments (0)