Proofing & Mailing¶
Personalization Layer¶
Image personalization is the process of rendering unique front/back postcard images per recipient — inserting names, discount codes, and other dynamic data into the design template. The personalization layer has three rendering engines:
| Engine | Status | Description | Repo |
|---|---|---|---|
| Paper Run Figma Plugin | Current | Renders designs via Figma plugin, uploads to Cloudflare | figma-plugin |
| Paper Run Backup Generator | Live (fallback) | Server-side renderer using Pixelixe under the hood, uploads to Cloudflare | paperrun_image_exporter |
| Pixelixe | Deprecated | Direct Pixelixe API calls, does NOT upload to Cloudflare | core/integrations/pixelixe.py |
The rendering engine is selected per-campaign based on the template's provider_type. Both the Figma Plugin and Backup Generator produce Cloudflare URLs for front and back images. Pixelixe (deprecated) returns its own URLs directly.
Personalization Layer repo: paperrun_image_exporter — contains the rendering gateway that routes to the appropriate engine.
Proofing¶
Two Celery tasks work in tandem using an SQS queue as a buffer:
Staging: stage_proofs¶
Schedule: Every 15 seconds State transition: Pending → ProofingStaged
- Atomically grabs up to 50 Pending recipients via
atomically_batch_update_and_retrieve_recipient_statuses() - Enqueues them to SQS via
bulk_enqueue_for_proofing()
Processing: dispatch_proofing_queue¶
Schedule: Every 7 seconds State transition: ProofingStaged → Proofing → Proofed
- Spawns multiple workers (count =
config.QUEUE_DISPATCH_COUNT) - Each worker dequeues up to 10 messages from SQS
- For each recipient:
- Sets status to
Proofing - Calls
make_proof()→runner.make_image_proofs()→ rendering engine - If using Figma Plugin or Backup Generator: images are uploaded to Cloudflare
- Sets status to
Proofed - Deletes the SQS message
graph TD
A[Pending Recipients] --> B[stage_proofs]
B -->|batch of 50| C[SQS Proofing Queue]
C --> D[dispatch_proofing_queue]
D --> E{Rendering Engine?}
E -->|Figma Plugin| F[Figma Plugin renders]
E -->|Backup Generator| G[Backup Generator renders]
E -->|Pixelixe| H[Pixelixe renders]
F --> I[Upload to Cloudflare]
G --> I
H --> J[Use Pixelixe URLs directly]
I --> K[Proofed]
J --> K
classDef process fill:#e1f5ff;
classDef success fill:#e1ffe1;
classDef decision fill:#fff3e0;
class A,B,C,D process;
class F,G,H,I,J decision;
class K success;
Mailing¶
Same two-task pattern as proofing:
Staging: stage_mailers¶
Schedule: Every 15 seconds State transition: Proofed → SendingStaged
Processing: dispatch_mailing_queue¶
Schedule: Every 7 seconds State transition: SendingStaged → Sending → Sent
- Dequeues recipients from SQS
- Selects printer via
generate_printer_strategy(): - LOB — US printer
- IntelliPrint — UK/NL printer
- Stannp — UK printer
- Printer creates the mailpiece, returns tracking info
- Saves mailpiece, updates Stripe billing meter, submits Klaviyo event
- Cleans up Cloudflare images (if applicable)
Printer Error Handling¶
Printer errors are classified by provider and retryability:
PrinterError— Fatal error. Requiresproviderargument (e.g.,"lob","intelliprint"). Recipient moves toErrorstatus.RetryablePrinterError— Transient error (HTTP 500, 502, 520, 522, 524, 525). The SQS message is left in place for automatic retry instead of marking the recipient asError.
Per-Printer Approval Rules¶
Before mailing, recipients are validated against printer-specific Pydantic models:
LobRecipientApproval— LOB-specific address/format rulesIntelliprintRecipientApproval— IntelliPrint-specific rules
Recipients that fail approval are set to Skipped with reason PrinterApprovalFailed. Validation errors are stored in recipient.json["approval_errors"] for debugging.
Image Handling by Printer¶
All printers apply size-appropriate Cloudflare variants via their gateway's size_variant_map. Sizes without an explicit variant fall back to public (a passthrough configured at max 30000x30000, fit:scale-down).
Campaign preview images are hosted with the public variant intentionally: previews show the rendered source creative, while printer-specific variants are applied only when a gateway submits a mailpiece.
Key Files¶
- Proofing:
celery/proofer.py,methods/proofs.py,methods/runner.py - Mailing:
celery/mailer.py,methods/runner.py,methods/mailpieces.py - Rendering engines:
core/integrations/internal/figma_plugin_generator.py,core/integrations/internal/pr_image_generator.py,core/integrations/internal/rendering_gateway.py,core/integrations/pixelixe.py - Printers:
core/integrations/printers/lob.py,core/integrations/printers/intelliprint.py,core/integrations/printers/stannp.py - Printer errors:
core/integrations/printers/errors.py - Approval rules:
core/integrations/printers/approval_rules.py - Image hosting:
core/integrations/cloudflare_images.py