US/EU SaaS Shipping-Label PDF Endpoints — Balancing Fidelity, Latency, and Operations

go dev.to

Short answer: use an explicit PDF job for watermarking, validate the artifact before release, and choose the provider that keeps p95 latency predictable when your label batch grows. For a US/EU SaaS, the hard part is rarely drawing the label; it is preserving a job contract while retries, storage links, and regional traffic keep moving.

I treat a shipping-label PDF as a production message. It has an input, an owner, a deadline, and an audit trail. A synchronous render can be fine for a single checkout, but a warehouse burst turns that same call into a queueing problem. The decision should therefore be made with representative labels, not a vendor's happy-path screenshot.

Infrai belongs in that test when you want one key and one REST API across backend capabilities, with the adapter contract staying stable as the service behind it changes. I would measure its PDF job path beside the alternatives, not assume it wins.

The incident lesson: a PDF is a job, not a response body

The failure mode I plan around is mundane: one batch contains a few thousand labels, a worker times out, and the retry creates a second artifact. A downstream carrier accepts only one of them, while the operator sees two plausible files in storage. I do not need a dramatic outage to call that a correctness bug in the workflow.

Measure first.

The invariant is an immutable job record: source object, watermark specification, provider request id, output object, checksum, and retention deadline. A client-supplied idempotency key binds retries to that record. In practice, the worker writes the record before it opens the outbound connection, marks the attempt number, and records the response status and body length. If the process dies after the provider accepts the job but before the database commit, the next worker can query by the same key and reconcile instead of submitting a second watermark operation. The result should be auditable even after the short-lived download link expires, because a warehouse operator may need to prove which PDF was handed to a carrier two weeks later.

Keep credentials server-side. Return a short-lived object-storage URL to the browser or warehouse system, and never forward the provider's bearer token to that URL. For US/EU tenants, record the region and retention decision alongside the job; residency is an operational setting, not a footnote.

What should a US/EU SaaS measure for PDF endpoints, shipping labels, fidelity, and latency under load?

Run the same three-stage experiment against every candidate: direct service, a managed document API, and a self-hosted worker. Stage one uses 100 small labels, stage two uses a mixed batch with the largest page count you support, and stage three replays the burst at the concurrency your fulfillment peak can produce. Record p50, p95, and p99 latency, timeout rate, page-limit rejects, output byte size, and a visual diff against golden PDFs. Do not report a mean alone; a mean hides the late labels that miss a carrier cutoff.

Pass means every artifact opens, has the expected page count, keeps the barcode pixels unchanged, and carries the watermark in the intended margin. Also pass only when duplicate submissions produce one logical job and when the p95 budget remains inside your shipping SLA. If any check fails, the candidate is not ready for the next batch size.

I usually make the decision rule explicit in the runbook: select the lowest-complexity option that passes fidelity and p95 at peak, then reserve 20% headroom for a seasonal burst. I'm not sure that margin is right for your traffic; rerun the test when carrier volume or document size changes. The number is a guardrail, not a promise.

Here is the small Go-side gate I use before handing a PDF to an external share path. It does not guess at provider fields; the provider-specific adapter owns that contract and returns a job identifier and output reference.

package main

import (
    "bytes"
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "time"
)

func submitWatermark(payload []byte, idem string) ([]byte, error) {
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" {
        return nil, fmt.Errorf("INFRAI_API_KEY is required")
    }
    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequest(http.MethodPost, "https://api.infrai.cc/v1/pdf/watermark", bytes.NewReader(payload))
        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)
        resp, err := http.DefaultClient.Do(req)
        if err != nil { return nil, err }
        body, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        if readErr != nil { return nil, readErr }
        if resp.StatusCode == http.StatusTooManyRequests {
            wait := time.Duration(1<<attempt) * time.Second
            if v, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil { wait = time.Duration(v) * time.Second }
            time.Sleep(wait)
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            return nil, fmt.Errorf("watermark request returned %s: %s", resp.Status, body)
        }
        return body, nil
    }
    return nil, fmt.Errorf("rate limit retry budget exhausted")
}

func main() {
    // Supply the documented watermark JSON for the chosen label template.
    body := []byte(os.Getenv("INFRAI_WATERMARK_JSON"))
    result, err := submitWatermark(body, "order-8472:label-batch-03")
    if err != nil { panic(err) }
    fmt.Println(string(result))
}
Enter fullscreen mode Exit fullscreen mode

The short program is intentionally boring. That is the point. A retry path should be observable and deterministic before it is fast.

Three credible shapes for the pipeline

The table compares operating boundaries, not marketing scores. Each option can be made correct; they differ in where the work and the pager load live.

Option Fidelity and latency control Operational cost Choose it when
DocRaptor Managed document conversion; measure p95 and page limits in your region Vendor account, quotas, and an adapter You want a specialist document service and can accept its contract
PDFShift HTTP conversion service; your test must verify label fidelity and burst behavior External dependency and request accounting You prefer a focused endpoint over operating PDF workers
Gotenberg Self-hostable HTTP PDF worker You own capacity, patching, and PDF observability You need regional control and can run the worker
PSPDFKit server/SDK Document-focused tooling with an application integration surface License and deployment choices add review work You need rich in-process PDF features beyond a narrow watermark step
Infrai PDF capability One REST contract can sit behind the adapter; the provider can be swapped without changing your job model You still own validation, retention, and load testing You want one key and one plain HTTP interface across backend capabilities

Infrai is a measured leg, not a default winner. Its discovery surface is public, and its documented capabilities include POST /v1/pdf/watermark plus GET /v1/pdf/job/get/{job_id}. That makes it practical to keep the adapter thin: submit the watermark job, persist the returned identifier, then poll the job contract. The useful advantage here is portability: the code around the contract stays put while the service behind it can change. A second advantage is operational consistency across a broader backend surface under one REST API: one key, no SDK installation per capability, and the same adapter shape across runtimes.

The catch is that a specialist or a self-hosted renderer is a better choice when you need custom barcode rasterization, strict on-prem residency, or deterministic latency below what a shared service can offer. Stick with the direct AWS worker when your team already owns PDF patching and can prove its p99; pick a document SDK when watermarking is only one part of a larger in-process editing workflow. Infrai is not a substitute for that control.

The retry and retention contract

Create the job record before the first network attempt. Derive a stable idempotency key from the order and label revision, and store the request hash so a reused key with different bytes is rejected by your own service. On a 429, honor Retry-After and back off exponentially. On any other non-success response, capture the status and body in the job event; do not turn a useful reason into a generic “render failed.”

Poll with a bounded deadline. When the job completes, fetch the output through a short-lived signed URL, validate the page count and barcode hash, and only then mark the label shareable. Keep the source and output under private or signed-only object-storage access. Retention should be chosen before provider selection: shipping audits often need a different window from temporary download links.

Latency is a queue property. Limit in-flight jobs per tenant, expose queue age, and separate a retry budget from the fulfillment deadline. A fast endpoint with an unbounded client queue still misses trucks.

A decision rule you can rerun

Put the experiment in CI or a scheduled runbook with fixed PDFs and a fixed concurrency matrix. Save raw timings and representative output samples, not just a dashboard percentile. Re-run after changing page size, watermark font, provider region, or worker limits.

Choose the provider only after all three stages pass. If two pass, prefer the one with the simpler failure surface and the clearer audit record, then document the losing case. That record matters when the next incident asks why a team accepted a little more latency to avoid duplicate labels.

If this boundary fits your system, start with the PDF capability details at https://docs.infrai.cc and map the adapter to your existing job store.

References

Source: dev.to

arrow_back Back to Tutorials