Integrate a direct mail platform
Learn how to integrate a direct mail platform with Klaviyo.
Overview
This resource will help you as you build an integration between a direct mail platform and Klaviyo. It walks through why direct mail belongs in Klaviyo, the bidirectional integration flow (audience sync and flow-triggered sends into your platform, mail lifecycle events back into Klaviyo), the optional profile properties to sync, and the authentication and certification requirements, with an API quick reference at the end.
Why connect your direct mail platform to Klaviyo
Direct mail is the channel that reaches customers email and SMS can't, and the brands getting real ROI from it don't run it in a silo. They run it from the same place they run every other channel, triggered by the same behavioral data, measured in the same reporting. When that platform is Klaviyo and mail is wired in natively, you get measurable wins:
- Trigger mail from the flows merchants already build. Abandoned cart, winback, post-purchase, VIP. A merchant can drop a direct mail send into an existing Klaviyo flow the same way they'd add an email or SMS step, with the same segmentation and timing logic.
- Reach who email and SMS can't. Unsubscribed contacts, chronically unengaged profiles, and anonymous or address-only audiences. Direct mail fills the gaps digital leaves behind, and Klaviyo segments are the cleanest way to define exactly who to reach and who to suppress.
- Spend less by suppressing the wrong people. Suppression audiences (recent purchasers, already-engaged, do-not-mail) are the highest-conviction use case in direct mail. Mailing is expensive per piece, so excluding the people who don't need it is where the savings are.
- Close the loop on attribution. Delivery scans, QR / personalized-URL scans, and redemptions flowing back onto the profile let the merchant answer "did the postcard drive the purchase?" and report direct mail ROAS next to email and SMS.
- Stickier merchants. A live, bidirectional direct mail integration is one of the strongest retention signals across the partner program. Mail tied to flows and attribution rarely gets turned off.
Short version: Klaviyo audiences and flows trigger your mail; your mail's delivery and response events flow back onto the Klaviyo profile. Those two directions are how every successful direct mail integration is built.
The flow
Direct mail is bidirectional. Unlike a one-way push integration, value comes from both directions working together:
- Klaviyo → your platform. Audience sync (a Klaviyo list or segment becomes a mail audience) and event-triggered sends (a Klaviyo flow webhook fires a send for a single contact). This is how the merchant decides who gets mail.
- Your platform → Klaviyo. Mail lifecycle and response events written back onto the profile. Mailpiece delivered, QR scanned, offer redeemed, purchase attributed. This is how the merchant measures and re-targets off mail.
Each profile is keyed by email first, with phone where you have it. The physical mailing address is your deliverability field, not a Klaviyo identity key; carry it on your side and, when you resolve one from an email (address-match), write it back as a property rather than treating it as a merge key. Use the merchant's customer ID as external_id where it's stable, but always identify by email first.
On initial install, mirror the merchant's chosen Klaviyo audiences (see Audience sync). No historical backfill of past mailings is needed; direct mail is forward-looking. Start writing lifecycle events from the first campaign you send.
Audience sync and triggering: Klaviyo → your platform
There are two ways merchants put Klaviyo data to work in direct mail. Most integrations support both.
Audience sync (mirror a list or segment)
Mirror a Klaviyo list or segment as a mail audience in your platform. There is no webhook for list/segment membership changes today, so maintain a local snapshot and compute the daily delta yourself, the same pattern the ad-audience integrations use.
- Let the merchant pick an audience. Show a dropdown of their Lists and Segments. Lists are static; segments are dynamic ("lapsed VIPs," "abandoned cart, no purchase in 14 days"). Most mail use cases target segments.
- Pull current membership. Page through
GET /api/lists/{id}/profilesorGET /api/segments/{id}/profiles. Grab email and, where present, name and address properties. - Store a snapshot keyed by Klaviyo list/segment ID. This is the input to tomorrow's diff.
- Run a daily diff. ADD = in today, not yesterday. REMOVE = in yesterday, not today. Push adds into the mail audience and drop removes (and honor suppression).
- Replace yesterday's snapshot with today's.
Daily is the right default cadence; the native reference integrations sync roughly daily and offer a manual re-sync. Map each Klaviyo list/segment to exactly one mail audience, with Klaviyo as the source of truth.
Event-triggered sends (flow webhook)
For one-to-one, behavior-triggered mail, the merchant adds a webhook step to a Klaviyo flow that POSTs a single contact to your send endpoint. Support two modes:
- Full contact. The flow has a complete mailing address (e.g., from the commerce profile). Send name + address and mail immediately.
- Email-only (address-match). The flow only has an email. Accept the email, resolve a deliverable postal address on your side, and mail if a match is found. This is what lets merchants mail anonymous or address-less audiences.
Have the merchant configure a Klaviyo flow webhook action pointed at your endpoint, with auth carried in headers and a JSON body you define. Document the exact body shape (see API quick reference). Differentiate multiple campaigns from one endpoint with a custom field on the payload rather than a separate URL per use case; it keeps the merchant's setup simple and is the pattern the reference integrations have converged on.
Event sync: your platform → Klaviyo
This is the bidirectional half, and the part merchants increasingly expect. As each mailpiece moves through production, delivery, and response, write an event back onto the matching Klaviyo profile. Once these events are on the profile, the merchant can use mail data anywhere they use Klaviyo data: flows, segments, suppression, and reporting.
Event names are not prefixed with your platform name. Branded events handle source attribution automatically when events flow through your OAuth token, so the metric name should just describe the action. Use Title Case payload field names ("Campaign Name", "Delivered At") so they read cleanly in the Klaviyo property picker and template editor.
Mail lifecycle and response events
| Event | When it fires | Payload |
|---|---|---|
| Mailpiece Created | A piece is queued / enters production for a contact. | Campaign ID, Campaign Name, Mailpiece ID, Format (postcard / letter / catalog / ...), Audience |
| Mailpiece Mailed | The piece is handed to the carrier (USPS). | Campaign ID, Mailpiece ID, Mailed At, Expected In-Home Date |
| Mailpiece In Transit | First carrier scan after handoff (IMb). | Campaign ID, Mailpiece ID, Carrier, Scanned At |
| Mailpiece Delivered | Carrier confirms in-home delivery (delivery scan / expected in-home date). | Campaign ID, Mailpiece ID, Delivered At |
| Mailpiece Returned To Sender | The piece is undeliverable and returned. | Campaign ID, Mailpiece ID, Return Reason |
| Mailpiece Scanned | Recipient scans the QR code or visits the personalized URL (pURL). | Campaign ID, Mailpiece ID, Scanned At, Destination URL, Offer Code |
| Offer Redeemed | The mail offer / coupon code is redeemed. | Campaign ID, Mailpiece ID, Offer Code, Order ID, Reward Value |
| Purchase Attributed To Mail | A purchase falls inside the mail attribution window (or matches a holdout test). | Campaign ID, Mailpiece ID, Order ID, $value, Attribution Window, Holdout Group |
| Address Matched | An email-only contact is resolved to a deliverable address. | Mailpiece ID, Match Source, Address On File (true) |
$value guidance: lifecycle events (created, mailed, delivered, scanned) are status updates, not revenue, so don't set $value on them. Set $value to the order total on Purchase Attributed To Mail (and on Offer Redeemed if it carries the order value) so the merchant sees direct mail revenue and ROAS per metric in Klaviyo reporting.
How merchants actually use it
- Delivered → follow up digitally. Segment on Mailpiece Delivered in the last N days to trigger a reinforcing email or SMS while the postcard is on the kitchen counter.
- Scanned but didn't buy. "Mailpiece Scanned in last 7d" AND NOT "Placed Order in last 7d" → a follow-up flow. The direct mail analog of the highest-value ad use case.
- Suppress who already got mail. Build a segment of profiles with a recent Mailpiece Mailed event and exclude them from the next drop, or suppress recent online purchasers from the mail audience to stop paying to mail people who already converted.
- Attribution and ROAS. Mail events on the profile alongside email opens, SMS, and orders let the merchant report what direct mail actually drove, including holdout-group lift.
Profile properties to sync (optional)
Optional, written inline on any event. Use Title Case property names prefixed with your platform's name. Replace {Platform} below with the literal name of your platform (e.g., "Acme Last Mailpiece Status"). These give the merchant a current-state view of each contact for segmentation.
| Property | What it is |
|---|---|
| {Platform} Last Mailpiece Status | created / mailed / delivered / returned. |
| {Platform} Last Delivered At | Datetime of the most recent in-home delivery. ISO 8601 (UTC). |
| {Platform} Last Campaign | Name or ID of the most recent campaign mailed to this profile. |
| {Platform} Total Mailpieces | Lifetime count of pieces mailed to this profile. |
| {Platform} Address On File | Boolean. Whether you hold a deliverable address (incl. address-match results). |
| {Platform} Mail Suppressed | Boolean. Whether the contact is on your do-not-mail / suppression list. |
Implementation notes:
- Profile property writes go through Update Profile (
PATCH /api/profiles/{id}/), or are written inline on Create Event (POST /api/events/). Inline on the event is the right default. - Pick property names up front and don't rename them. Renames break every segment, flow, and template that references them.
- Keep dates in ISO 8601 (UTC). Klaviyo treats string-typed dates as strings, which silently breaks date-property segmentation.
- Don't overwrite profile name or address from your platform unless you are the address's source of truth. The merchant's commerce integration usually owns identity; write address-match results as your own namespaced property, not onto the core profile address.
Auth, scopes, and certification
Build as a public OAuth app. Private API keys are a non-starter for the marketplace. Start with the Create a public OAuth app guide.
Key OAuth specifics for direct mail integrations:
- PKCE is required on the authorization code exchange (
code_verifiermust be included). - Access tokens are short-lived (1 hour). Refresh tokens are long-lived but rotate on every use. Persist the latest refresh token after every refresh call, or you'll lose the integration on the next refresh.
- Tokens are scoped per Klaviyo account. Merchants with multiple Klaviyo accounts (brands, regions, prod and test) install separately for each.
Scopes to request:
lists:readandsegments:readto populate the audience picker and pull membership for audience sync.profiles:readto read the email, name, and address properties needed to build a mailpiece, and to match contacts.profiles:writeto write your optional mail-status properties (and to create profiles for address-matched leads that don't exist yet).events:writeto emit mail lifecycle and response events back onto the profile. This is the scope that makes the integration bidirectional.
Don't over-scope. Request only what your flows actually use; unused scopes get flagged in marketplace review. Branded metric badges happen automatically when events flow through the OAuth token. No extra payload field is needed.
Once functional, submit for App Marketplace review under the Direct Mail category. Plan for at least one round of feedback. The review covers OAuth, scopes, install and uninstall validation, and that the integration delivers what your listing claims.
API quick reference
Concrete specs for AI coding assistants and engineers grounding on this doc. The narrative above is the why and the flow. This section is the what-to-actually-type.
Headers (required on every request)
Authorization: Bearer <oauth_access_token>
revision: 2026-04-15
Accept: application/json
Content-Type: application/json
Pin the revision header to a specific date when you ship. Check the API versioning guide for the current latest. Without it you'll get a 400.
Pulling audience membership
Cursor-based pagination via JSON:API. Don't compute offsets; follow the cursor until links.next is null. Use the is_active filter so empty segments don't clutter the picker.
GET /api/segments/{id}/profiles?page[size]=100
→ response.links.next contains the full next-page URL
→ loop until links.next is null
GET /api/segments?filter=equals(is_active,true)
Event POST body (mail lifecycle)
Send a mail event. The event creates the profile if it doesn't exist, and updates the optional mail-status properties on the profile inline. Identify by email first.
POST /api/events/
{
"data": {
"type": "event",
"attributes": {
"properties": {
"Campaign ID": "camp_4821",
"Campaign Name": "Winback Q2",
"Mailpiece ID": "mp_99175",
"Format": "postcard",
"Delivered At": "2026-06-15T00:00:00Z"
},
"metric": {
"data": {
"type": "metric",
"attributes": { "name": "Mailpiece Delivered" }
}
},
"profile": {
"data": {
"type": "profile",
"attributes": {
"email": "[email protected]",
"properties": {
"{Platform} Last Mailpiece Status": "delivered",
"{Platform} Last Delivered At": "2026-06-15T00:00:00Z",
"{Platform} Last Campaign": "Winback Q2"
}
}
}
}
}
}
}
Attributed purchase with revenue
When a purchase falls inside the mail attribution window, include $value so the merchant sees direct mail revenue and ROAS per metric.
POST /api/events/
{
"data": {
"type": "event",
"attributes": {
"properties": {
"Campaign ID": "camp_4821",
"Mailpiece ID": "mp_99175",
"Order ID": "order_98765",
"Attribution Window": "30d",
"Holdout Group": false,
"$value": 128.00
},
"metric": {
"data": {
"type": "metric",
"attributes": { "name": "Purchase Attributed To Mail" }
}
},
"profile": {
"data": {
"type": "profile",
"attributes": { "email": "[email protected]" }
}
}
}
}
}
Inbound trigger payload (Klaviyo flow webhook → your endpoint)
This is the body the merchant's Klaviyo flow webhook POSTs to YOUR send endpoint. Define it on your side; document both the full-contact and email-only shapes. Klaviyo flow webhooks support a custom JSON body, so the merchant can map profile fields directly.
POST https://api.yourplatform.com/v1/{connection_id}/send
{
"reference_id": "{{ person.external_id }}",
"email": "{{ person.email }}",
"firstname": "{{ person.first_name }}",
"lastname": "{{ person.last_name }}",
"line1": "{{ person.street_address }}",
"city": "{{ person.city }}",
"state": "{{ person.region }}",
"postal_code": "{{ person.zip }}",
"country": "{{ person.country }}",
"custom_1": "abandoned_cart"
}
For the email-only (address-match) mode, send just the email plus custom fields, resolve the address on your side, and mail if matched. Carry auth in request headers (a per-connection token), and use a custom field (e.g., custom_1) to identify the trigger so one endpoint can serve many campaigns.
Retries
- 429: respect the
Retry-Afterheader. Otherwise exponential backoff starting at 1s, cap at 60s, max 5 retries. - 5xx: same backoff.
- 4xx (other than 429): don't retry. Log and surface to the merchant.
Identifier guidance
- Identify by email first; phone where you have it. These are the fields Klaviyo merges on.
- The mailing address is a deliverability field, NOT a Klaviyo merge key. Don't try to identify or merge profiles on address.
external_idis NOT a merge key on its own. Pass it alongside email and store the merchant-side customer ID as a profile property for segmentation.- Event metrics require a profile with an email. If a mailpiece has no email (pure prospecting list), you can't write an event back; track it on your side until an email is known.
Rate limits
Per-account, not per-app. Daily audience pulls and per-campaign event writes sit well under standard limits. For very large drops (hundreds of thousands of pieces generating delivery and scan events), batch event writes with Bulk Create Events (POST /api/event-bulk-create-jobs/) and reserve POST /api/events/ for the real-time hot path. For merchants at unusual scale, request per-endpoint, per-account increases through Klaviyo Support and CSM before launch rather than ramping into 429s in production.
Glossary
- Klaviyo profile: customer record. Keyed by email, phone, or external ID.
- Klaviyo metric: event type. "Mailpiece Delivered," "Offer Redeemed," etc. Created automatically on first event ingest.
- Klaviyo segment / list: dynamic vs. static audience. The merchant mirrors these as mail audiences and uses them for targeting and suppression.
- Mailpiece: a single physical piece (postcard, letter, catalog) mailed to one contact.
- Address-match / MailMatch: resolving a deliverable postal address from an email-only contact so anonymous or address-less audiences can be mailed.
- pURL / QR scan: a personalized URL or QR code on the piece; the scan is the primary direct mail response signal.
- Suppression audience: a list of profiles to exclude from a mailing (recent purchasers, do-not-mail), the highest-value cost-saving use case.
- Holdout group: a randomly withheld portion of an audience used to measure true incremental lift from mail.
Resources
- Klaviyo Developer Portal. Start here for everything.
- Create a public OAuth app
- Get Profiles for List / Get Profiles for Segment
- Create Event / Bulk Create Events / Update Profile
- Klaviyo App Marketplace, Direct Mail category. The existing set of direct mail integrations.
- App Marketplace Review: [email protected]
Updated about 16 hours ago