Automation-driven operations — freeing sellers from repetitive manual work.
Background
Day-to-day POD operations on Temu involve a large amount of manual work: filling product Excel sheets according to category-specific rules, maintaining template SKUs with colors and sizes, uploading product images to cloud storage, parsing exported orders and matching customer-customized images, removing watermarks, generating mockups in batches, and finally handing orders to local production equipment. These workflows are tedious, error-prone, and had little reusable engineering foundation.
This project is the full-pipeline automation system I designed and continuously expanded after taking over these workflows as an engineer. The goal is not only to reduce clicks, but to bring products, assets, orders, production actions, and status reporting into one configurable, traceable, and extensible toolchain.
Vue 3 Composition API
TypeScript · Vite
Naive UI · Pinia
Vue Router · ofetch
useSSETaskuseOrderTaskusePlanEditorPython 3.12 · Uvicorn
Pydantic · SQLModel
Alembic · boto3
google-genai · Playwright
PostgreSQL — Image library metadata
SQLite — Order history
JSON — SKU mapping · Resource index
Cloudflare R2 — Image CDN
Each datasource chosen for its access pattern
Business Loop
Template management, SKU/color/size metadata, title libraries, print libraries, tag imports, PSD assets, and image-status checks.
Plan multi-store upload tasks, generate Temu / Miaoshou Excel files, upload assets to R2, and support campaign and price-related operations.
Parse exported orders, validate customer-customized images, generate production assets, and sync tasks to local production nodes for printing and PLC dispensing.
After this expansion, the platform is no longer just an “Excel generation + image upload” tool. It has become a workspace for the full POD operation lifecycle: order processing, order management, ERP integration, campaign management, template management, Excel generation, image upload, AI watermark removal, print library, print tags, batch rendering, render jobs, log analytics, MCP integration, and production-node management. Long-running operations share one task interface and one SSE log-stream pattern, so operators no longer need to jump between spreadsheets, terminals, browsers, and local devices.
Technical Depth
All long-running tasks share the same problem: they take seconds to minutes, and users need real-time progress feedback. I chose SSE over WebSocket — pure HTTP unidirectional push, no protocol upgrade required, and a natural fit for FastAPI’s StreamingResponse. The backend assigns a UUID to each task; the frontend useSSETask composable manages connection lifecycle, giving all modules zero-boilerplate integration.
@router.get("/sse/{task_id}")
async def sse_stream(task_id: str):
async def event_generator():
queue = task_queues.get(task_id)
while True:
msg = await queue.get()
if msg is None: break # task done signal
yield f"data: {msg}\n\n"
return StreamingResponse(event_generator(), media_type="text/event-stream")Image upload is I/O-bound — network transfer dominates over CPU time. Using ThreadPoolExecutor(max_workers=48) compresses serial upload time to roughly 1/48. The value 48 was determined empirically as the optimal balance between R2 API rate limits and local bandwidth. After upload completes, the system automatically rebuilds the data.json resource index — the single data contract between the upload and order modules.
with ThreadPoolExecutor(max_workers=48) as executor:
futures = {executor.submit(upload_single, p, bucket): p for p in paths}
for future in as_completed(futures):
try:
future.result()
task_queue.put(f"✓ {futures[future].name}") # SSE push
except Exception as e:
failed.append((futures[future], str(e)))
rebuild_index(bucket) # rebuild resource indexCore principle: the view layer only renders; business logic lives in Composables. Four Composables each own their domain and compose with each other — useOrderTask internally composes useSSETask, reusing log-stream logic without duplication. TypeScript unified request/response types across 10 API modules, catching field-rename errors at compile time during multiple refactors.
useSSETaskgenericManages SSE connection lifecycle, exposes logs / isRunning — reused by all modules
useOrderTaskordersOrder selection, pre-processing, batch submission, failedImages state — composes useSSETask
usePlanEditorconfigYAML Plan CRUD: GET load → local edit → POST persist
useUploadEditoruploadUpload task config management, same pattern as usePlanEditor with added field validation
- ›Exposes HTTP endpoints only
- ›Pydantic input validation
- ›Calls Service, no business logic
- ›10+ domain-split routers
- ›Core business logic
- ›Independently unit-testable
- ›Cross-router logic reuse
- ›Encapsulates external API / device calls
- ›PostgreSQL — structured metadata
- ›SQLite — lightweight local history
- ›JSON/YAML — config and indexes
- ›R2 — image object storage
SQLModel (SQLAlchemy + Pydantic fusion) lets the same Model class serve as both ORM mapping and API schema, eliminating redundant definitions. Alembic manages PostgreSQL migration history so every schema change is versioned. The three datasources were chosen for their access patterns — not over-engineering, but the right tool for each job.
Traditional watermark removal relies on fixed-position masks, but product image watermarks vary in position, font, and opacity. By integrating Gemini 2.5 Flash multimodal, the system feeds the image together with natural language instructions, letting the model semantically understand and execute the edit.
The engineering focus was prompt engineering: a template structure of “describe task + constrain format + provide example”, with separate strategies for solid-color vs. complex backgrounds. Failed images enter a retry queue, with full SSE progress streaming throughout.
Once an order enters production, the master system is responsible for distribution and status recording, while a local production node runs on the machine connected to the printer and production line. The node syncs published orders, claims the next task, prints shipping labels through the local CUPS printer, executes SKU-ordered production actions, and reports key states such as claimed, running, printed, done, or failed back to the master system.
The PLC layer uses python-snap7 to connect to a Siemens S7-200 SMART controller. It supports connection testing, output-point testing, and order-line-based pulse signals. The device-communication layer is separated from the order model so future integrations can swap in serial, Modbus, or HTTP device interfaces without changing the master system.
Early scripts had parameters scattered through code: changing a store, template, or output directory meant editing scripts. The current system extracts task definition from execution logic. settings.yaml describes default paths and globals, catalog.yaml merges product catalog metadata, and upload_plan.json orchestrates date-based batch tasks. The same backend pipeline exposes both CLI and HTTP APIs: command line for development/debugging, frontend buttons for operations.
POD product imagery is not just image upload. It depends on base mockups, prints, colors, customization type, and output format. The platform separates template configuration, print tags, PSD import, region configuration, and rendering jobs into dedicated modules. Template pages manage product metadata, the print library manages assets and labels, batch rendering composites prints into mockups, and the render-job page tracks asynchronous task state.