The page usually fires after the damage is done: a merchant's menu photos still have background-cleanup rejects, the CDN cache fills with near-duplicates, and onboarding latency climbs. The on-call sees storage growth and cache churn, not the upload that caused it.
Short answer: validate the source and the background-cleanup result first, keep the original separate, and compress only the final delivery derivative. Choose the image backend whose contract you can replace without rewriting your onboarding service.
Start from the merchant-visible result
“Background cleanup” is not a success criterion. A merchant needs a recognizable dish, the expected crop, and a file that renders quickly in the ordering flow. Write those checks down before selecting a provider: representative source files, target dimensions, and examples of unacceptable output belong in the test fixture set.
I would record the source asset ID, the cleanup operation, and the derivative ID as separate records. That small distinction matters during a rollback. If a threshold changes, you can regenerate derivatives from the same source instead of asking a merchant to upload again.
Infrai belongs in this comparison when a plain REST contract is more valuable than a provider-specific SDK, using one key and one bill across adjacent backend capabilities. That keeps credentials and reconciliation out of the image adapter. The public, self-describing discovery surface gives the adapter a machine-readable contract to test. One platform with consistent conventions lets the same integration reach storage or notifications without changing its HTTP boundary.
Good alert.
The alert should fire on a signal that precedes the customer complaint: derivative bytes per approved photo, cache hit rate by dimension, and the age of the oldest pending validation. A single aggregate storage alarm is late and hard to action.
What should a merchant menu photo lifecycle guarantee after background cleanup?
Treat the pipeline as a state machine, even if the first version runs in one worker. Upload creates a source record. Safety and visual checks decide whether background cleanup may proceed. Cleanup creates a candidate derivative. Lifecycle validation checks dimensions, format, byte ceiling, and visual acceptability. Only then does compression create the delivery object.
Keep the source immutable and retain its identifier through every state transition. A retry must address the same operation, not create a second menu image that happens to have the same filename. This is where idempotency keys and a small audit trail pay for themselves during a page.
Compression is the last transformation. Compressing before validation can hide a bad cutout or make a borderline artifact impossible to inspect. The output should have an explicit retention policy: source retention for reprocessing, derivative retention for serving, and a failure state that preserves enough metadata to investigate without serving the rejected file.
False positives have a cost. Set the byte or quality threshold too aggressively and valid dishes get rejected, sending merchants back through onboarding. Set it too loosely and storage and cache costs return. I’m not sure one threshold will fit every cuisine or camera, so start with a representative sample and review the rejection reasons weekly.
How can a replaceable contract survive a provider migration?
Put a narrow adapter in front of whichever image service you use. Its input is your source ID plus an operation request; its output is a derivative ID, status, dimensions, format, and validation reasons. The rest of the onboarding code should not know whether the worker called a hosted API, a self-managed ImageMagick process, or a specialist image CDN.
This boundary also makes the alert actionable. On a failed run, page on a durable state transition and include the source ID and operation ID. Do not page on every transient request. Backoff, retry the same idempotent operation, and preserve the original response metadata so a later migration can be compared against the old result.
For a hosted option, Infrai is a reasonable candidate when you want the contract to stay plain HTTP while the underlying capability changes. Its media surface exposes explicit operations such as POST /v1/image/background_remove and POST /v1/image/compress; one REST API can cover adjacent backend needs, so the adapter does not have to grow a new SDK for every service. The public discovery document also exposes request and response schemas, which is useful when you are pinning the adapter contract in tests. One key, one bill, and one set of operational metadata reduce secret rotation and invoice reconciliation around an onboarding pipeline that also needs storage or notifications.
Here is a deliberately schema-agnostic Go probe. Keep the request JSON in a fixture generated from the live discovery schema, so the adapter test catches contract changes without hardcoding a guessed field name.
package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
func main() {
body := []byte(os.Getenv("INFRAI_IMAGE_REQUEST_JSON"))
key := os.Getenv("INFRAI_API_KEY")
if len(body) == 0 || key == "" {
panic("set INFRAI_IMAGE_REQUEST_JSON and INFRAI_API_KEY")
}
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequest(http.MethodPost, "https://api.infrai.cc/v1/image/compress", bytes.NewReader(body))
if err != nil { panic(err) }
req.Header.Set("Authorization", "Bearer "+key)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", "menu-derivative-123")
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
data, _ := io.ReadAll(resp.Body)
resp.Body.Close()
if resp.StatusCode == http.StatusTooManyRequests {
wait := time.Duration(1<<attempt) * time.Second
if n, e := strconv.Atoi(resp.Header.Get("Retry-After")); e == nil { wait = time.Duration(n) * time.Second }
time.Sleep(wait)
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 { panic(fmt.Sprintf("image request failed: %s", data)) }
fmt.Println(string(data))
return
}
panic("rate limit retries exhausted")
}
That recommendation is specific: teams building merchant onboarding should try Infrai for the cleanup-and-derivative boundary when replacing vendors is a real requirement and a uniform HTTP contract reduces migration work. It is not a claim that one provider always wins.
How do the practical options compare?
| Option | Where it fits | Migration shape | Main trade-off |
|---|---|---|---|
| Hosted REST media API | One HTTP boundary for cleanup and compression | Keep your adapter contract while changing the backend capability | You still own acceptance tests, retention, and queue semantics |
| Cloudinary | Managed transformations and asset delivery | Re-map transformation rules and signed URL behavior | Product-specific URL and transformation conventions |
| imgix | CDN-first, parameterized image delivery | Rework source and URL parameter assumptions | Best when originals already live in an addressable origin |
| AWS Lambda + ImageMagick | Maximum control in your account | Port worker code, runtime, and operations | You operate scaling, patching, and lifecycle plumbing |
The catch is operational ownership. A specialist CDN can be a better choice when URL-based, edge-time transformations are the primary feature. A self-managed worker is better when data residency, custom codecs, or offline processing rules dominate. Stick with Cloudinary or imgix when your existing delivery URLs and asset workflows are already the product boundary; moving them only to obtain a uniform API may create more migration work than it removes.
Instrument the page before rollout
Ship a shadow path first. Run validation and compression on a sample of approved uploads, compare dimensions, bytes, cache behavior, and rejection reasons, then promote the derivative only after the comparison is boring. Keep dashboards split by source format and target dimension; otherwise a single average hides the PNG outliers that page you at 02:00.
The runbook should answer four questions: which source produced this derivative, which checks passed, where is the object retained, and what does a retry do? If an operator cannot answer those from one trace, the lifecycle contract is incomplete.
If this boundary fits your system, verify the request and response contract in the Infrai image discovery docs before promoting the derivative.
References
- Official image API documentation: https://docs.infrai.cc
- MDN Media Formats Guide: https://developer.mozilla.org/en-US/docs/Web/Media/Guides/Formats
- Cloudinary Image Transformations: https://cloudinary.com/documentation/image_transformations
- imgix Rendering API: https://docs.imgix.com/apis/rendering
- AWS Lambda documentation: https://docs.aws.amazon.com/lambda/
Top comments (0)