KamoCRM

Image attachments are drawn from a 480px preview, not the original

FeatureMediaService
Shipped
September 24, 2026 at 11:27 PM UTC
Author
Kamo
Commit
bc18063

A chat bubble draws an image at most 240x240 CSS px and every viewer downloaded the original to do it — 3-8 MB per phone photo. GET **************** now answers a WebP bounded to 480px on its long edge; the original endpoint, the lightbox and downloads are unchanged. One preview per stored file (ImgDat), not per attachment: <imgDatId>_thumb_480.webp in the imaging bucket, beside the original. The _thumb infix is what SecurityService's OrgObjectStorageSweep bills as a derivative of that dat. It is deliberately not the imaging library's _thumb_p0.png (a 640px PNG DocsService serves under document rights and flags on the shared dat). Made at upload and on first request. Both attach paths (upload, and the dedup link) queue it in the background; a request that finds none makes it on the spot via ConversionService /api/conversion/image/preview — so every existing attachment gets one the first time anyone looks, with no backfill. Concurrent requests for the same content join one generation; at most two run per pod; the background queue drops rather than grows. A refusal (animation, undecodable, not smaller) is remembered as a zero-byte object and the original is served from then on; a failure (busy, down, a 404 from a ConversionService without the endpoint) is not remembered. Whenever there is no preview the endpoint serves the original exactly as /imaging/proxy/{id} does, so an <img> pointed at it always gets a picture. X-Attachment-Rendition says which. Access: both endpoints go through one authorize() — authentication, existence, revocation, message removal, missing content, session membership, same order, same answers — before the preview store is touched. A revoked attachment's preview is refused at the same instant as its original. ImagingProxyPreviewAccessTest pins each refusal to the original's (status and body, GET and HEAD); a negative control (membership check skipped for the preview) fails it. A preview GET records the member's first download, as the whole-file GET of the original did when bubbles drew the original. Deploy AFTER ConversionService f7729d9 (the endpoint). Before it lands every preview request fails soft to the original; nothing is marked.

All changes

Like what you see shipping?

All of it arrives in your workspace on its own. Start on the free plan and read this page again in a month.

Start Free ForeverView Pricing