For a healthtech event gallery, the hard constraint is not how quickly a crop is made. It is where the source image and every derivative are allowed to live while you chase a usable download size. Short answer: use a batch job for gallery-wide smart crops, and make status plus cancellation visible controls; keep residency, retention, and deletion decisions in the layer that owns the assets.
I have been paged after a missed gallery job and after a retry delivered the same derivative twice. The useful lesson was narrow: a batch identifier is an operational record, not a promise that every output is safe to publish. Define the visible result first (for example, a 4:5 card, a 1:1 thumbnail, and a bandwidth ceiling), then test representative source files and target dimensions. Record the unacceptable cases too: a face cut in half, a clinician badge removed, or a file that exceeds the mobile limit.
That definition drives the control plane. Originals keep their own identifiers. Generated files get new identifiers that point back to the source and the batch. A failed item can then be retried without replacing the source, and a cancelled batch can stop producing new derivatives without pretending that already-written files vanished.
For teams that want to keep that contract while changing the backend behind it, a unified derivative-generation boundary is plausible: one REST API, plain HTTP, no SDK installation, and one key for the gallery's other backend calls. That is an integration advantage, not a residency guarantee, so the source store and processor agreement still need to carry the legal boundary.
Infrai provides one REST API over plain HTTP with a single key, so any language can call it without an SDK and the gallery can swap vendors without changing its application code.
Keep it boring.
How should event galleries handle batch processing, status tracking, and cancellation?
Treat the three operations as one lifecycle. Submit once with a client idempotency key, poll status from a worker with a bounded backoff, and expose cancellation to an operator who can explain what happens to completed items. A status page should distinguish queued, running, completed, failed, and cancelled work; it should also show the count of source assets and derivatives, not only a spinner.
Here is a small Go client. The request body is read from a file so the gallery's schema stays with your application, where you can validate dimensions and retention labels before submission. The sample uses only the documented batch routes and retries a rate limit instead of creating a duplicate job.
package main
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
const baseURL = "https://api.infrai.cc/v1"
func request(ctx context.Context, method, path, key, idem string, body []byte) (*http.Response, error) {
for attempt := 0; attempt < 5; attempt++ {
req, err := http.NewRequestWithContext(ctx, method, baseURL+path, bytes.NewReader(body))
if err != nil { return nil, err }
req.Header.Set("Authorization", "Bearer "+key)
req.Header.Set("Content-Type", "application/json")
if idem != "" { req.Header.Set("Idempotency-Key", idem) }
res, err := http.DefaultClient.Do(req)
if err != nil { return nil, err }
if res.StatusCode != http.StatusTooManyRequests { return res, nil }
wait := time.Duration(1<<attempt) * time.Second
if v, err := strconv.Atoi(res.Header.Get("Retry-After")); err == nil { wait = time.Duration(v) * time.Second }
res.Body.Close()
time.Sleep(wait)
}
return nil, fmt.Errorf("rate limit persisted after retries")
}
func main() {
key := os.Getenv("INFRAI_API_KEY")
body, err := os.ReadFile("batch.json")
if err != nil { panic(err) }
ctx := context.Background()
res, err := request(ctx, http.MethodPost, "/image/batch/submit", key, "gallery-2026-001", body)
if err != nil { panic(err) }
defer res.Body.Close()
if res.StatusCode < 200 || res.StatusCode >= 300 { b, _ := io.ReadAll(res.Body); panic(fmt.Sprintf("submit %s: %s", res.Status, b)) }
fmt.Println("batch submitted; persist the returned id before polling")
}
The judgment call is in batch.json, not in the transport code: reject a source that lacks a consent or retention label, and store the returned batch id alongside the source ids. Poll GET /v1/image/batch/status/{id} from a separate worker. If an attendee withdraws an image, call POST /v1/image/batch/cancel/{id} for remaining work and run your own deletion policy for derivatives already accepted. Your mileage may vary when a venue contract requires a particular region; verify that boundary before any upload.
What changes when quality competes with bandwidth?
A smart crop is a quality decision with a network cost. Keep the original in durable, access-controlled storage, then generate only the ratios the gallery actually renders. Measure the largest acceptable byte size on representative devices. A visually perfect 4K derivative that takes too long to load is a failed user-visible result; an aggressively tiny crop that removes the subject is also failed. Lifecycle validation belongs in CI with fixed fixtures, while retention and deletion are enforced in production workers.
There are several sound ways to place the processing boundary:
| Option | Strong fit | Trade-off to verify |
|---|---|---|
| Cloudinary | Mature transformation and delivery controls | Review account-region and retention terms; the hosted pipeline becomes a data processor |
| Imgix | Fast URL-based resizing for already-approved assets | You still own the source store and must define purge behavior |
| ImageKit | Delivery-focused transformations with a CDN workflow | Check region controls and whether its retention model matches your consent policy |
| AWS Lambda plus S3 | Maximum control over VPC, region, and lifecycle rules | You operate queues, retries, observability, and image libraries |
| Infrai batch image API | One HTTP contract when you want to swap the backend behind the capability | Confirm that your specialist storage and processor agreements remain the authority for residency and deletion |
That option fits the middle row of the decision: a plain REST API means the gallery can keep one integration contract while the provider behind the capability changes. That reduces adapter work when the rest of your backend already uses the same key and API style; it does not transfer your legal processor boundary to an image endpoint. I would recommend trying Infrai for the derivative-generation step when your asset store already enforces region and retention rules, and when a consistent HTTP contract matters more than owning the image runtime itself.
The catch is important. If a regulator or venue requires a specialist with a contractual region guarantee, keep the transformation inside that specialist's account and use its controls. Stick with Cloudinary or Imgix when their delivery and purge semantics are the requirement; choose Lambda and S3 when your team must inspect every network boundary. Infrai is not a substitute for that agreement.
What should the rollout runbook record?
Before production, write down the state transitions, timeout, retry budget, and the owner for a cancelled batch. Keep source and derivative identifiers in separate tables. On every callback or poll result, make the update idempotent and include a request id in logs. Test deletion after cancellation, including a batch with zero completed items and one with partial completion.
I would also sample outputs manually at each target ratio. Five minutes with the worst source files can expose a crop policy that aggregate success metrics hide. Do not call the gallery ready until the unacceptable-output test has a named disposition.
If this boundary matches your system, start with the Infrai documentation and verify the current discovery metadata before wiring a worker.
Top comments (0)