# 8xSend > Lifecycle email for apps and websites. Your app POSTs small JSON lifecycle events; 8xSend owns contacts, consent, flows (welcome, onboarding, abandoned checkout, trial expiry, dunning, win-back), sending from your domain, and deliverability. Catalog version 2026-10-20. ## Integrate in one sitting 1. Get an ingest key from a human (8xSend → Settings → API keys, or the MCP tool issue_ingest_key). It starts with 8xs_ing_ and is a SERVER-SIDE secret. Never ship it to a browser or mobile app. 2. Install the SDK: `npm i @8xsend/node`, then `const send = new EightXSend({ baseUrl: "https://8xsend.com", key: process.env.EIGHTXSEND_INGEST_KEY })` and `await send.track("user_signed_up", { id, email, name, locale, marketing_consent }, { signup_method: "google" })`. Or POST the envelope yourself — the schema is at https://8xsend.com/api/ingest/v1/schema.json and the OpenAPI at https://8xsend.com/api/ingest/v1/openapi.json. 3. Emit user_signed_up on account creation and account_deleted on deletion. Then the billing events. Then a daily cron for subscription_expiring and user_inactive. 4. Confirm arrival: MCP check_events / recent_events, or 8xSend → Contacts → Events inspector, which also lists anything rejected and why. ## Rules that bite - event_id is the idempotency key: unique per real occurrence, reused on retries. "signup:" is a good pattern. 8–200 chars. Dedupe is permanent. - occurred_at is ISO 8601 WITH a timezone (Z or offset). When it happened, not when you sent it. - user.id is your stable internal id, never the email. Contacts match by id first, then email. An email change is NOT propagated. - Money is integer minor units (cents) plus an ISO 4217 currency. Never floats. - user.marketing_consent:true only moves pending→subscribed and never re-subscribes an unsubscribed contact; to re-subscribe, send marketing_consent_changed {subscribed:true}. Omitting consent applies the project's default (implied or explicit). - Unknown event names are rejected (422). Anything bespoke is event "custom" with properties.name. - A batch (up to 500) can return 202 with rejected>0 and an errors array: the good ones were processed, the bad ones were not. Read it. Retry 429 and 5xx (503 = transient) with the same event_ids. - Property rules are advisory at the API (warnings in the 202) and strict in the SDK types. - Segment-style calls are accepted on the same endpoint: {type:"track", event, userId, messageId, timestamp, properties, traits} and {type:"identify", userId, messageId, timestamp, traits}. ## Events Tier 1 (required): - user_signed_up — A new account is created. Properties: signup_method?:string, referrer?:string, campaign?:string. - account_deleted — The user deletes their account / requests erasure. Properties: reason?:string. Tier 2 (monetisation and retention): - user_activated — The user hits their 'aha' — their first real action (you define it). Properties: activation_type?:string. - trial_started — A free trial begins. Properties: plan:string, trial_ends_at:datetime. - subscription_started — The user becomes paying (first paid sub). Properties: plan:string, price_cents:integer, currency:string, interval:string, from_trial?:boolean. - subscription_renewed — A subscription renews. Properties: plan:string, price_cents:integer, currency:string. - subscription_expiring — Daily cron: N days before a trial/sub lapses. Properties: plan:string, expires_at:datetime, price_cents?:integer, currency?:string. - subscription_cancelled — The user turns off auto-renew (still has access). Properties: plan:string, reason?:string, access_until:datetime. - subscription_expired — Access actually ends. Properties: plan:string. - purchase_completed — A one-time / consumable purchase. Properties: product_id:string, name?:string, item_name?:string, price_cents:integer, currency:string, quantity?:integer. - payment_failed — A charge is declined (card expired, bank hold, insufficient funds). Properties: plan?:string, amount_cents:integer, currency:string, attempt?:integer, next_retry_at?:datetime, reason?:string, store?:string, manage_billing_url?:string. - payment_recovered — A previously failed charge finally succeeds. Properties: plan?:string, amount_cents:integer, currency:string. - user_inactive — Daily cron: no activity for N days. Properties: last_active_at:datetime, days_inactive:integer. Tier 3 (leads, abandonment, engagement): - waitlist_joined — Someone joins a pre-launch waitlist. Properties: email?:string, source?:string. - email_captured — A lead email arrives from any other source. Properties: email:string, source:string. - checkout_started — The user begins a purchase / upgrade. Properties: checkout_id:string, value_cents:integer, currency:string, item_name?:string, items?:array. - checkout_completed — The purchase finishes. Properties: checkout_id:string, order_id:string, value_cents:integer, currency:string, item_name?:string. - feature_used — The user uses a notable feature. Properties: feature:string, count?:integer. - marketing_consent_changed — The user toggles email opt-in in your app. Properties: subscribed:boolean, source?:string. - contact_imported — A contact is added from a list — 8xSend sends this itself for every row of a CSV import. Properties: list:string, flow_id?:string. - affiliate_applied — Someone applies to your affiliate or creator program. Properties: program?:string, application_id?:string, source?:string. - custom — Any other event you define — put a name in properties. Properties: name:string. ## Billing connectors (RevenueCat, Stripe) Don't hand-wire subscription events. Connect RevenueCat or Stripe in 8xSend → Settings → Sending and their webhooks become trial_started, subscription_started/renewed/cancelled/expired, payment_failed (with store) and payment_recovered. RevenueCat apps must call Purchases.logIn(userId) and set the $email subscriber attribute; Stripe apps set metadata.user_id on subscriptions and checkout sessions. ## Outbound webhooks (8xSend → your app) Consent notices and flow webhook steps are POSTed with X-8xsend-Timestamp (unix seconds) and X-8xsend-Signature: v1=. Reject timestamps older than 5 minutes. ## Docs - [Playbook](https://8xsend.com/api/docs/playbook.md): What to do for your play: upgrade one that already has flows, or add one for the first time. - [Setup guide](https://8xsend.com/api/docs/play-setup.md): Take a project from nothing to sending real lifecycle email. - [Events API reference](https://8xsend.com/api/docs/events-api.md): The full ingest contract: envelope, canonical events, consent, idempotency. - [Flow catalog](https://8xsend.com/api/docs/flows.md): Every library template: trigger, filters, step-by-step timing, and the metric it moves. - [Claude Code (MCP)](https://8xsend.com/api/docs/mcp.md): Set a play up, install flows and write its email copy from the play's own repo. - [JSON Schema](https://8xsend.com/api/ingest/v1/schema.json) - [OpenAPI](https://8xsend.com/api/ingest/v1/openapi.json) - [MCP server](https://8xsend.com/api/mcp): Streamable HTTP, Bearer 8xs_mgmt_… key, 25 tools for setting a project up from its own repo.