Send emails and transactions via the Adapty Mail API
The Adapty Mail API lets you send user profiles and transactions to Adapty Mail directly from your server, without routing data through the Adapty SDK. Use it when you want to:
- Add subscribers when you don’t have a base in Adapty Mail yet.
- Reuse the subscriber base from your other apps.
- Feed Adapty Mail server-to-server, with your backend as the source of truth.
API or SDK? Most apps send data to Adapty Mail through the Adapty SDK, which collects emails and purchases automatically — see Connect Adapty to Adapty Mail. Choose the API when your app has no Adapty SDK, when the data already lives on your server, or when you import subscribers from another source. You can also use the API alongside another entry point. Adapty Mail matches people by email address. Two sources reporting the same address land on one profile; two different addresses create separate profiles, even when customer_user_id is identical.
Before you start
Finish setting up Adapty Mail before you send data — that means a campaign, segments (if you need them), a web paywall, and a launched flow. Adapty Mail sends emails only to profiles created after this setup is complete; profiles you send earlier won’t receive any emails. Follow Get started with Adapty Mail first, then return here.
You also need your API key and base URL:
- Secret API key: In Adapty Mail, go to Settings > Project and copy the Secret key. The key is project-specific, so the API knows which project the data belongs to.
- Base URL: All requests go to
https://api-mail.adapty.io. - Authentication: Send the key in the Authorization header as
Bearer {your_secret_api_key}.
Get explicit consent before you collect emails and send them to Adapty Mail. You are responsible for compliance with GDPR, CAN-SPAM, and similar regulations in your markets.
Send user profiles
A profile carries the user’s email and attributes. To create or update one, send a POST request to /api/v1/profile/save/.
Three fields are required:
- A stable
external_profile_idthat your app or backend owns - The
emailAdapty Mail delivers campaigns to external_created_at— the user creation time, which you can use in segments
Always send a stable external_profile_id, never an anonymous or per-install value. Adapty Mail uses it to link emails, clicks, and purchases to one profile.
If the same person also reaches Adapty Mail through another entry point, send customer_user_id — the same user ID that source sends. For the Adapty SDK that’s the value you pass to Adapty.identify(); FunnelFox sends the one it holds. It doesn’t decide which profile they land on; the email address does that. What it adds is a second way to find the profile: a transaction event carrying it resolves without an email, and a deletion can name it instead of the address.
curl --request POST \
--url 'https://api-mail.adapty.io/api/v1/profile/save/' \
--header 'Authorization: Bearer {your_secret_api_key}' \
--header 'Content-Type: application/json' \
--data '{
"external_profile_id": "user_12345",
"external_created_at": "2026-06-01T10:30:00Z",
"email": "jane@example.com",
"country": "US",
"custom_attributes": {
"plan": "trial"
}
}'
country and store_country take a two-letter ISO 3166-1 alpha-2 code — US, not USA or United States. Any other value is rejected.
device_info describes the user’s device. The object is optional, but platform must be present whenever you send it.
| Field | Optional | Segment filter | Notes |
|---|---|---|---|
platform | No | Yes | |
device | Yes | Yes | The device model. |
os | Yes | Yes | |
locale | Yes | Yes | |
app_version | Yes | Yes | Compared in version order, so 1.10.0 ranks above 1.9.0. |
timezone | Yes | No | Sets when the user receives email. Without it, Adapty Mail treats them as UTC, so the 08:00–21:00 send window follows UTC rather than their own day. |
See the Save profile reference for every available field.
Send transaction events
A profile with an email is enough to reach users in the never purchased flow. Users in every other flow also need transaction events.
Every flow except never purchased is driven by purchase history. Send a profile’s transaction events as you handle purchases, renewals, and cancellations, so Adapty Mail can place it in the right flow. Transaction events also power revenue attribution. Skip them only if you run never purchased campaigns exclusively.
To record a transaction, send a POST request to /api/v1/profile/transaction-event/save/. Use the same external_profile_id you sent with the profile so Adapty Mail links the transaction to the right user.
curl --request POST \
--url 'https://api-mail.adapty.io/api/v1/profile/transaction-event/save/' \
--header 'Authorization: Bearer {your_secret_api_key}' \
--header 'Content-Type: application/json' \
--data '{
"event_type": "subscription_started",
"event_id": "evt_abc123",
"event_datetime": "2026-06-10T14:20:05Z",
"external_profile_id": "user_12345",
"store": "app_store",
"store_product_id": "premium_monthly",
"store_transaction_id": "1000000123456789",
"store_original_transaction_id": "1000000123456789",
"purchased_at": "2026-06-10T14:20:00Z",
"originally_purchased_at": "2026-06-10T14:20:00Z",
"price_usd": "9.99"
}'
See the Save transaction event reference for every available field.
You can send a transaction before the profile exists. Adapty Mail keeps the event unattached, then links it to the profile on the next save that includes the same external_profile_id.
Delete a profile
To fulfil a right-to-erasure request, send the profile’s identifiers to /api/v1/profile/delete/. Adapty Mail erases the profile’s personal data and cancels its scheduled emails.
curl --request POST \
--url 'https://api-mail.adapty.io/api/v1/profile/delete/' \
--header 'Authorization: Bearer {your_secret_api_key}' \
--header 'Content-Type: application/json' \
--data '{
"external_profile_id": "user_12345"
}'
Identify the profile by external_profile_id, customer_user_id, or email — send at least one. Adapty Mail clears the stored data, but it doesn’t block the address: if a source sends it again, a new profile appears with no history. To keep someone out for good, stop the source from sending their data.
A delete request that times out leaves you unsure whether it landed, so you send it again. That second call is safe when you identify the profile by external_profile_id or customer_user_id — it succeeds and changes nothing. A second call carrying only email returns 404, because the first deletion cleared the address it would have matched. The Delete profile reference lists the response codes.
Map your events to flows
Send the event_type that matches what happened. Adapty Mail derives the profile’s state from its event history and routes it to the matching flow.
event_type | Send it when | Flow |
|---|---|---|
subscription_started | A user starts a new subscription. | Active — no re-engagement flow |
subscription_renewed | A subscription auto-renews. | Active — no re-engagement flow |
subscription_renewal_reactivated | A user turns auto-renew back on. | Active — no re-engagement flow |
non_subscription_purchase | A user makes a one-time purchase. | Active — no re-engagement flow |
subscription_renewal_cancelled | A user turns off auto-renew (still active until expiry). | Renewal cancelled |
billing_issue_detected | A renewal payment fails. | Billing issue |
entered_grace_period | Payment fails but the user is still in a grace period. | Billing issue |
subscription_expired | A subscription lapses and access ends. | Expired |
subscription_refunded | A subscription purchase is refunded. | Refunded |
non_subscription_purchase_refunded | A one-time purchase is refunded. | Refunded |