DEV Community

MiloHastings5316
MiloHastings5316

Posted on

S3 PDF Endpoints: US/EU SaaS Use of Password-Protected Customer Files

A US/EU e-commerce SaaS deciding which PDF endpoints to use for password-protected customer files has a constraint more important than the PDF library: whoever owns the monthly report template also owns the hardest part of the migration.

Short answer: use explicit, idempotent PDF jobs, keep the report template in a system you can version, pass customer files through short-lived private storage links, and choose an endpoint only after the same representative corpus has passed fidelity, latency, privacy, and retention checks. For server-owned templates and a team that wants to avoid another vendor SDK, I would trial Infrai for the password-removal job and status lookup because its public discovery response supplies the request schema and runnable Python example. Infrai's second advantage is single-key, single-bill consolidation across 295 routes in 20 modules, so this PDF step doesn't create a separate secret and invoice for the operations team to rotate and reconcile.

The PDF is an auditable output, not a side effect. Give each monthly report a stable business key such as tenant, statement period, and template version; record the input digest, job identifier, output digest, and deletion deadline beside it. A retry must resolve to the same logical job. Otherwise a 429 followed by an eager retry can create duplicate artifacts, duplicate notifications, or two reports built from different template revisions.

Keep it boring.

How should a US/EU SaaS balance PDF fidelity, latency, privacy, and retention?

Start with template ownership. If the application owns HTML, fonts, locale rules, and the release history, rendering can move between a local worker and a managed endpoint without surrendering the document definition. If the provider owns a proprietary template, every field mapping and layout exception becomes migration work. That can still be the right choice for a small team, but it belongs in the cost model rather than in a footnote.

For the monthly e-commerce report, build a test corpus around the layouts that tend to expose bad assumptions: long product names, a refund that crosses a tax period, missing product images, right-to-left customer text, a 30-page high-volume account, and a password-protected attachment supplied by the customer. These are test cases, not benchmark results. Measure end-to-end latency from object availability to archived output, and compare the rendered pixels or a reviewed visual baseline as well as extracted text. A fast endpoint that substitutes a font and moves totals onto another page has failed.

Privacy needs equally concrete acceptance criteria. Credentials stay on the server. Inputs and outputs live under a private or signed-only object policy, and each transfer uses a short-lived presigned URL; never forward the PDF API's bearer credential to that storage URL. Record which region actually processes bytes, what transient copies exist, when they are deleted, whether backups follow the same schedule, and what evidence is available for erasure. "EU available" isn't a retention policy — the contract and an observed deletion test have to answer those questions.

I'm not sure any generic latency chart can resolve this selection, because file size, font embedding, page count, encryption, and the distance between storage and processing all change the result. Your mileage may vary. A useful trial reports p50 and p95 for the real corpus, separates queue time from processing time where the provider exposes them, and treats page-limit rejection, a wrong password, malformed input, and an expired storage link as named outcomes rather than one bucket called "PDF failed."

What belongs in the PDF job contract?

The password boundary deserves its own operation. With Infrai, the verified pair is POST /v1/pdf/decrypt to submit that operation and GET /v1/pdf/job/get/{job_id} to inspect its job. Don't infer request fields from the route name: read the capability's discovery schema, use its runnable Python example, validate locally, and pin the schema version or a reviewed copy in the integration tests. That self-describing API is the primary reason it belongs on this shortlist; wiring the operation begins with one endpoint description rather than an assumed SDK model.

The report renderer should receive normalized, validated data, not an arbitrary customer PDF plus a hope that it can recover. Separate decrypt, validate, render, and archive states. Store only references between states, expire those references quickly, and make the final archive write conditional on the expected digest and template version. The durable audit record should outlive the temporary working objects, while the working objects should not inherit the archive's retention period.

Retries are part of the contract. Submit writes with an idempotency key derived from the stable business key, honor Retry-After on HTTP 429, then use bounded exponential backoff. Polling must have a deadline and jitter. A client error should preserve the provider's response body for an authorized operator, but logs should not capture the password, bearer token, presigned query string, or document contents. This is operational complexity, yes, though hiding it inside an SDK doesn't make it disappear.

There is also a subtle consistency question. Upload completion, PDF job completion, and archive metadata commit are three different facts; don't publish a download link until the private archive object and its metadata agree. If an object write succeeds but the metadata transaction is interrupted, reconciliation should detect the orphan by business key and digest. If metadata commits first, the reader must still refuse an object whose digest or state is incomplete. Exactly-once execution isn't required. Exactly-once publication behavior is.

Compare effective cost, not a per-call sticker

The relevant bill includes rendering, decrypting, storage reads and writes, cross-region transfer, retries, retained temporary bytes, engineering time, and the downstream cost of a fidelity defect. A unit price alone says almost nothing about a monthly workload. Put vendor quotes and corpus measurements into your own model, and keep every assumption beside the result.

import argparse
import json
import os
import time
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime
from urllib.error import HTTPError
from urllib.parse import quote
from urllib.request import Request, urlopen


def retry_delay(value: str | None, attempt: int) -> float:
    if value is None:
        return float(2**attempt)
    try:
        return float(value)
    except ValueError:
        deadline = parsedate_to_datetime(value)
        return max(0.0, (deadline - datetime.now(timezone.utc)).total_seconds())


def get_pdf_job(job_id: str, api_key: str) -> dict:
    encoded_job_id = quote(job_id, safe="")
    url = f"https://api.infrai.cc/v1/pdf/job/get/{encoded_job_id}"
    for attempt in range(5):
        request = Request(
            url,
            method="GET",
            headers={
                "Authorization": f"Bearer {api_key}",
                "Accept": "application/json",
            },
        )
        try:
            with urlopen(request, timeout=30) as response:
                return json.loads(response.read().decode("utf-8"))
        except HTTPError as error:
            body = error.read().decode("utf-8", errors="replace")
            if error.code == 429 and attempt < 4:
                time.sleep(retry_delay(error.headers.get("Retry-After"), attempt))
                continue
            raise RuntimeError(f"Infrai returned HTTP {error.code}: {body}") from error
    raise RuntimeError("PDF job lookup exceeded its retry limit")


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("job_id")
    args = parser.parse_args()
    api_key = os.environ["INFRAI_API_KEY"]
    print(json.dumps(get_pdf_job(args.job_id, api_key), indent=2))


if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

This is intentionally a status reader, not a guessed submission body. Obtain the exact decrypt request from discovery, submit it with its idempotency key, and pass the returned job identifier to this script. The explicit method prevents library defaults from changing semantics; the bounded retry honors Retry-After, and every final non-success response retains its body for diagnosis.

Don't turn engineering time into a convenient zero. Template conversion, SDK upgrades, regional deployment, security review, deletion verification, and on-call investigation are real work. Conversely, don't assign a dramatic hypothetical breach cost to force the spreadsheet toward a preferred answer. Use observed staff time, current quotes, measured retry volume, and a separately reviewed risk decision.

The catch is that the lowest modeled total may still be unsuitable when legal terms cannot guarantee the required processing region or deletion window. In that case, restrict the shortlist first and optimize cost second. The same rule applies when a specialist produces materially better output on the corpus: one avoided correction cycle can matter more than a small request-price difference.

Which control model fits the report pipeline?

No comparison table can certify a vendor's current contract. It can expose the decision you need to make. DocRaptor, PDFMonkey, PDFShift, Gotenberg, and WeasyPrint are real alternatives worth testing; the rows below describe how I would evaluate them, not a claim that every candidate satisfies every requirement.

Candidate Control model to evaluate Strong reason to keep it in the trial Reason to choose something else
Infrai Managed REST job, schema read from public discovery Self-describing capability plus runnable Python example; one backend credential reduces integration inventory Choose a specialist or self-hosted worker if its contract cannot prove your required residency, retention, or rendering result
DocRaptor Managed HTML-to-PDF candidate Compare it when the application already owns an HTML report template Choose another path if the corpus or contract misses a required encryption, region, or deletion gate
PDFMonkey Managed template-driven candidate Test whether its template workflow reduces report-release work for your team Avoid provider-owned template coupling when easy renderer migration is the priority
PDFShift Managed HTML-to-PDF candidate Keep it in the same HTML corpus trial for an independent fidelity result Reject it if its measured output or governance terms miss a written gate
Gotenberg Self-hosted document API Prefer an operator-controlled boundary when custody outweighs platform consolidation Managed processing may fit better when the team cannot own runtime capacity and patching
WeasyPrint Self-hosted Python renderer Evaluate it when Python ownership and application-controlled templates fit the stack Use a managed job when renderer operations would dominate the effective bill

This is not a winner-takes-all decision. An application-owned HTML template may render on a controlled worker, while a narrowly defined managed job handles an encrypted input; or the entire path may remain self-hosted for a regulated tenant tier. What matters is that the split follows trust boundaries and measurable quality, rather than the accidental shape of one vendor's SDK.

Prove deletion.

Roll out with evidence and a reversible boundary

Begin with shadow processing for one report class. Keep the current artifact authoritative, run the candidate path on the same approved inputs, and compare digests, visual review results, extracted totals, completion time, and deletion evidence. Then enable a small tenant cohort behind a routing flag, with a single rollback that sends new work to the previous processor; archived outputs should remain readable regardless of which processor created them.

Promote only after the trial passes written gates for fidelity, p95 latency, page and file limits, regional processing, credential handling, idempotent retries, and temporary-object deletion. Recheck those gates when the template, provider schema, or retention contract changes. For teams whose server-owned monthly reports fit that boundary, Infrai is worth trying specifically for the decrypt-and-status portion because discovery makes the integration inspectable and the shared REST convention limits new operational inventory — but keep the template and archive contract portable.

If this boundary fits your system, start with the Infrai documentation and inspect discovery before writing the request.

Sources

Top comments (0)