Order syncing data flow¶
Overview¶
Order syncing is the process that closes the loop between sending postcards and measuring their impact. Every 5 minutes, a background job (process_orders) fetches new orders from each organization's integration provider (Klaviyo, Ometria, or Shopify) using checkpoint-based incremental syncing — it picks up where the last sync left off.
For each order, the system attempts to attribute it to a campaign using a cascading match hierarchy:
- Email match — does the order email match a sent recipient's email?
- Physical address match — does the shipping address match a recipient's address? (skips if the customer had an active subscription before the mail was sent)
- Discount code match — does the order include a campaign or recipient-specific discount code? (the only method that works for unsent recipients)
- Holdout match — if no experimental match was found, does the order match a holdout recipient? (uses
created_atinstead ofsent_atsince holdouts never receive mail)
The first method that matches wins. Attributed orders are saved to the database with an unprocessed attribution record, which a separate background job later validates against timing windows and business rules.
For the full attribution logic — timing constraints, holdout analysis, edge cases — see docs/attribution_methodology.md.
Flowchart¶
graph TD
A[Background Scheduler] -->|Every 5 minutes| B[process_orders]
B --> C[get_organizations]
C --> D{For each organization}
D --> E{sync_orders enabled?}
E -->|No| F[Skip organization]
E -->|Yes| G{organization enabled?}
G -->|No| F
G -->|Yes| H[sync_organization_page_order]
H --> I[get_mail_integration]
I --> J{Integration exists?}
J -->|No| K[Return - no integration]
J -->|Yes| L[fetch_integration_order_events]
L --> M{Integration Provider?}
M -->|Klaviyo| N[get_klaviyo_client]
M -->|Ometria| O[OmetriaClient]
M -->|Other| P[Log warning - unsupported]
N --> Q[find_placed_order_metric]
Q --> R[find_events_for_metric]
R --> S[Convert to OrderEvent]
O --> T[list_orders since checkpoint]
T --> U[Convert to OrderEvent]
S --> V[Process Events]
U --> V
V --> W{Events found?}
W -->|No| X[Log no orders]
W -->|Yes| Y[get_campaigns_by_organization]
Y --> Z[For each order_event]
Z --> AA[process_order]
AA --> BB{Valid org domain?}
BB -->|Company email| CC[Skip order]
BB -->|Valid| DD[attribute_order]
DD --> EE[check_for_matched_emails]
EE --> FF{Email match found?}
FF -->|Yes| GG[Set attributed_campaign]
FF -->|No| HH[check_for_physical_address_match]
HH --> II{Address match found?}
II -->|Yes| GG
II -->|No| JJ[Check discount codes]
JJ --> KK{Discount code match?}
KK -->|Yes| GG
KK -->|No| LL[check_for_holdout_order]
LL --> MM{Holdout match?}
MM -->|Yes| NN[Create holdout attribution]
MM -->|No| OO[No attribution]
GG --> PP[check_for_blacklist]
PP --> QQ{Attributed campaign > 0?}
QQ -->|Yes| RR[upsert_order_by_provider_id]
QQ -->|No| SS[Skip saving order]
RR --> TT[insert_unprocessed_attribution]
TT --> UU[Log OrderAttributed event]
NN --> VV[Log OrderHoldoutAttributed]
VV --> XX[upsert_attribution_by_provider_id]
UU --> ZZZ[Continue to next event]
SS --> ZZZ
XX --> ZZZ
OO --> ZZZ
ZZZ --> YYY{More events?}
YYY -->|Yes| Z
YYY -->|No| YY[Sort events by datetime]
YY --> ZZ[update_organization_order_checkpoint]
subgraph "Attribution Methods"
AAA[Email Match]
BBB[Physical Address Match]
CCC[Discount Code Match]
DDD[Holdout Attribution]
end
subgraph "Data Storage"
EEE[Redis Cache]
FFF[Database Orders]
GGG[Database Attributions]
HHH[Analytics Logs]
end
subgraph "External Providers"
III[Klaviyo API]
JJJ[Ometria API]
KKK[Future: Shopify API]
end