Skip to content

Campaign Recipients

Enums: CampaignRecipientStatus and CampaignRecipientSkipReason in app/core/models/campaign_recipients.py

States

graph LR
Pending --> ProofingStaged
Pending --> AddressEnrichable
Pending --> Skipped
Pending --> Holdout
ProofingStaged --> Proofing
Proofing --> Proofed
Proofing --> Error
Proofed --> SendingStaged
Proofed --> Scheduled
SendingStaged --> Sending
Sending --> Sent
Sending --> SendingStaged
Sending --> Error
Sent --> Received
Scheduled --> Sent
AddressEnrichable --> AddressEnriching
AddressEnriching --> Pending
AddressEnriching --> Skipped

classDef starting fill:#96d0ff;
classDef ending fill:#ffd6d6;
classDef static fill:#dccbff;
classDef paused fill:#fff2a8;

class Pending starting;
class Received ending;
class Holdout,Skipped,Error,Archived,Blacklisted static;
class AddressEnrichable,AddressEnriching paused;

Pending

Synced from integration. Ready for proofing.

ProofingStaged

Queued in SQS awaiting proof generation.

Proofing

Proof generation in progress (image personalization).

Proofed

Proof generated. Ready for mailing.

SendingStaged

Queued in SQS awaiting mailpiece creation.

Sending

Mailpiece creation in progress.

Sent

Mailpiece submitted to printer.

Received

Mailpiece delivered (tracked via printer webhooks).

Scheduled

Awaiting future mail date. Frozen until campaign completes.

Holdout

Control group member. Not mailed intentionally.

Skipped

Validation failed (see skip reason).

AddressEnrichable

Missing address but eligible for enrichment. When address enrichment is enabled on the campaign and the recipient has no address1, they get AddressEnrichable instead of being Skipped with NoAddress. This gates them into the enrichment flow without losing the recipient.

AddressEnriching

Address enrichment in progress via Faraday.

Error

Fatal error in proof/mailer/queue.

Archived

Excluded from campaign (legacy).

Blacklisted

On organization blocklist.

Skip Reasons

Reason Meaning
NoAddress No address1 provided, or enrichment failed.
International Country check or address validation failed.
CapExceeded Billing recipient cap reached.
EnrichmentFailed Faraday enrichment returned no result.
PrinterApprovalFailed Printer-specific rules rejected recipient.

Recipient Validation

Recipients are validated at two stages during sync:

Data Validation (sync time)

RecipientDataValidator in campaign_recipients.py is a Pydantic BaseModel that validates all incoming recipient data:

  • Whitespace stripping on all string fields
  • Required non-empty fields: address1, zip, country
  • Name rules: at least one of first_name or last_name required; asterisks rejected in names
  • Recipients that fail validation are Skipped with an appropriate reason

Printer Approval (pre-mailing)

Per-printer Pydantic approval models validate recipients against printer-specific rules before mailing:

  • LobRecipientApproval — LOB-specific address/format requirements
  • IntelliprintRecipientApproval — IntelliPrint-specific requirements

Recipients that fail approval are Skipped with reason PrinterApprovalFailed. Validation errors are stored in recipient.json["approval_errors"] for debugging.

See core/integrations/printers/approval_rules.py for the rule definitions.

Key files

  • Pipeline: celery/proofer.py, celery/mailer.py, methods/runner.py
  • Validation: methods/campaigns/recipient_sync.py
  • Enrichment: celery/address_enrichment.py
  • Holdouts: methods/recipients.py

app.core.models.campaign_recipients.ErrorMessages

Error messages set on campaign recipients.

BILLING_NOT_READY = 'Org billing is not ready to send' class-attribute instance-attribute

Preflight error.

Raised by preflight_campaign_billing when no usable billing path exists for the org (no Stripe customer, no active subscription, no legacy SubscriptionItem, or legacy price drift). Fails closed at the proofing/sending stage so we don't burn Pixelixe credits or print a mailpiece against an un-billable org.

COULD_NOT_GENERATE_CREATIVE = 'Could not generate creative for recipient' class-attribute instance-attribute

Printer error (Taylor).

This occurs when:

  • Image personalization service is unavailable
  • Template data is incomplete or invalid
  • Creative generation timeout
  • Missing front or back image assets
  • Pixelixe personalization API errors

FAILED_SUBMISSION_TO_MAILING_QUEUE = 'Failed submission to Mailing Queue' class-attribute instance-attribute

Mailing error.

This can happen when:

  • Mailing queue is down or unreachable
  • Authentication issues with mailing queue
  • Invalid mailing data format
  • Mailing queue quota exceeded

FAILED_SUBMISSION_TO_PRINTER = 'Failed submission to Printer' class-attribute instance-attribute

Printer error (Taylor).

Typical causes:

  • Printer service API is down or unreachable
  • Authentication issues with printer service
  • Invalid mail piece data format
  • Print service quota exceeded
  • Taylor/IntelliPrint/LOB API rejecting the submission

FAILED_SUBMISSION_TO_PROOFING_QUEUE = 'Failed submission to Proofing Queue' class-attribute instance-attribute

Proofing error.

This can happen when:

  • Proofing queue is down or unreachable
  • Authentication issues with proofing queue
  • Invalid proofing data format
  • Proofing queue quota exceeded

FAILED_TO_APPLY_DISCOUNT_CODE = 'Failed to apply discount code' class-attribute instance-attribute

Proofing error.

This can happen when:

  • Discount code generation service is down
  • Invalid discount code configuration
  • External e-commerce platform API issues
  • Shopify integration failures

FAILED_TO_MAKE_MAIL_PIECE = 'Failed to make mail piece for this recipient' class-attribute instance-attribute

Mailing error.

Common causes include:

  • Print service API failures (LOB, IntelliPrint, Stannp)
  • Invalid recipient address data
  • Mail piece template generation issues
  • Network connectivity problems with printer services

FAILED_TO_MAKE_PROOF = 'Failed to make proof for this recipient' class-attribute instance-attribute

Proofing error.

  • Image generation or personalization fails
  • Template rendering encounters issues
  • External proof generation service is unavailable
  • Pixelixe API is down or returns errors