How Docshy keeps files as ciphertext from phone to printer — and removes our copy when the job ends.
Problem in one sentence
Print shops need a temporary document. Messaging apps give them a permanent one. Docshy is a relay: encrypt → store briefly → decrypt only for print → shred.
This post is the architecture behind that relay as it runs in production today (docshy.com, GCP, region asia-south1 / Mumbai).
High-level topology
flowchart TB
C[Customer phone / app] -->|AES-256-GCM encrypt| U[Ciphertext upload]
U --> API[Cloud Run API]
API --> GCS[(Encrypted object storage)]
API --> R[(Redis — short-lived job state)]
API --> FS[(Control-plane metadata)]
API --> KMS[Cloud KMS]
API -->|Authorized print| S[Shop dashboard]
S -->|Print| P[Paper]
S -->|Job done| X[Shred our copy]
Design rule: the platform is a mere conduit. We optimize for short-lived encrypted blobs and explicit deletion — not a document archive.
Actors and trust boundaries
| Actor | Runs where | Trust assumption |
|---|---|---|
| Customer app | Browser / native shell loading docshy.com | Encrypts before upload |
| API | Cloud Run | Stores ciphertext; decrypts only on authorized print |
| Shop dashboard | Browser + shop auth | Sees plaintext at print time (required to print) |
| Object storage | Blind bucket | Ciphertext only |
| Redis | Shared job / queue store | Short TTLs — not the durable file store |
| KMS | GCP | Wraps per-file data keys |
Honest boundary: zero retention on our side ≠ zero visibility at the counter. The shop must render the page to print. Screenshots and photos of paper remain physical-world risks. Architecture removes the WhatsApp-forever + cloud-library path.
Crypto: double envelope
Layer 1 — Client (privacy layer)
- Algorithm: AES-256-GCM in the browser (Web Crypto)
- When: Before the upload leaves the device
- Why: Readable IDs/PDFs are not the default thing we receive from the customer path
Larger files use a chunked format so mobile browsers stay stable; small files encrypt in one shot.
Layer 2 — Platform (infrastructure layer)
After the upload is accepted:
- Generate a per-object data encryption key (DEK)
- Encrypt the payload again for storage (AES-GCM)
- Wrap the DEK with Cloud KMS
- Write ciphertext to object storage
- Keep short-lived job metadata (and wrapped key material) in Redis; mirror control data in Firestore where needed for ops
Property we care about: dumping job metadata or listing the bucket without KMS does not yield plaintext PDFs. GCM tags also fail decrypt if ciphertext is tampered with.
Request path: upload → queue → print → shred
1. Bind to a shop
Customer scans the shop’s static counter QR (HMAC-authenticated, shop-bound). That is the production default for busy counters. Legacy rotating tokens are still accepted where older dashboards mint them.
2. Upload
Customer encrypts on device → uploads ciphertext with shop binding → platform re-envelopes for storage → a print job lands in the shop queue. Soft-launch caps keep a visit to a small file set (scan again for the next set).
3. Shop queue
Authenticated shop owners see jobs for their shop only. Live queue state sits in Redis so multiple Cloud Run instances stay consistent; Firestore can reconcile when the hot set is empty.
4. Print (momentary plaintext)
On an authorized print action: verify shop ownership → unwrap DEK via KMS → stream decrypt to the shop browser for print.
This is the only intentional plaintext window on the server/shop path. Printers need pixels.
5. Shred
When the job completes (or via cleanup):
- Delete the encrypted object
- Clear job / queue / related meta
- Optionally notify the customer that it’s printed and gone
A scheduled sweep removes aged or already-printed leftovers so failures don’t leave ciphertext for days.
“Shred” means delete from our stores and clear pointers — not marketing-speak for wiping every disk sector. The guarantee: no retained product object after success or expiry policy.
State: what lives where
| Store | Holds | Lifetime intent |
|---|---|---|
| Object storage (GCS) | Encrypted print payloads | Until shred or policy expiry |
| Redis | Queues, job records, short relay temps, shop QR / session counters | Minutes–hours (TTLs) |
| Control plane DB (Firestore) | Jobs mirror, shop registry, usage | Ops/audit — not a second copy of file bytes |
| KMS | Key-encryption key for DEK wrap | Long-lived infra key |
Redis = brain of the live job. Object storage = blind bag of ciphertext. Confusing those two is how architecture posts go wrong.
Auth and abuse controls (architecture-relevant)
- Shop APIs require verified shop ownership
- Customer upload requires a valid shop QR binding
- Rate limits and strict browser security headers on the edge
- Signed shop tokens so QR codes can’t be trivially forged
- Liveness vs readiness: the process can be “up” while still reporting not ready if Redis is down (fail closed at scale)
DPDPA angle (technical, not legal advice)
DPDPA stresses purpose and not keeping personal data longer than needed. For a print job, purpose ≈ print.
| Mechanism | Effect |
|---|---|
| Client + server ciphertext | We don’t casually store readable docs by default |
| Short-lived objects + shred | Post-job retention on our storage is designed out |
| Shop dashboard instead of WhatsApp | Stops “staff phone gallery forever” as the transport |
| Mumbai region | Data path stays in-India for latency and trust |
What it does not do: replace shop policy, stop someone photographing the screen, or act as a regulator-issued compliance certificate.
Failure and cleanup modes
| Failure | Behaviour |
|---|---|
| Print never confirmed | TTLs + sweep still remove aged ciphertext/meta |
| Redis unavailable | Multi-instance consistency degrades; readiness fails closed |
| Partial shred | Cleanup reconciles printed/expired objects |
| KMS unavailable | Decrypt/print fails closed — no plaintext fallback |
What we’re hardening next
- Richer session ledger and shop settlement visibility (visit caps already live; full counter UI still maturing)
- Stronger audit visibility of upload → print → shred for operators
- Optional B2B surfaces for orgs that already own document workflows
Summary
Docshy’s production architecture is intentionally boring in the right places:
- Encrypt on device (AES-256-GCM)
- Re-envelope for storage (AES-GCM + Cloud KMS)
- Blind object storage + short-lived job state in Redis
- Static shop QR to bind upload to a counter
- Decrypt only on authorized print
- Delete ciphertext and clear job state when done (plus sweep)
That is the technical meaning of our zero-retention print relay: ciphertext for a short job, then removal — not a document CMS.
Abhinay Gokul Pulla
Founder & Director — Perceptova Intelligence Private Limited
Top comments (0)