M MemberIntel KB
Activity Decisions

decision

ADR-0017: MemberIntel Connect — WordPress Plugin as the Data-Sync Source

ADR-0017 (Accepted (V1 plugin shipped 2026-05-14; sync service deployed 2026-05-15), 2026-05-15): MemberIntel Connect — WordPress Plugin as the Data-Sync Source.

Status: Accepted (V1 plugin shipped 2026-05-14; sync service deployed 2026-05-15)
Date: 2026-05-15
Deciders: Seth (Lead Architect), Blair (CEO)

Context

MemberIntel needs read access to a customer’s MemberPress data: members, transactions, subscriptions, memberships, aggregate stats, and system info. The dashboard, HEARTBEAT generation (ADR-0015), and any future cross-pollination pipeline (ADR-0014, V2+) all depend on this data being in the MemberIntel backend’s Postgres, not behind a customer’s WordPress install.

Four data-access shapes were on the table:

  1. Custom WordPress plugin exposing REST endpoints — the MemberIntel backend pulls on a schedule.
  2. Direct MySQL access — connect to the customer’s WP database from MemberIntel’s backend.
  3. Webhook push — WP plugin sends events to MemberIntel on every member/transaction change.
  4. Generic WP REST API consumer — use WordPress’s built-in REST endpoints + a permissions plugin.

The forces:

  • Customer-controlled install. WordPress sites run anywhere — managed hosts, VPSes, customer-owned servers, behind WAFs. No assumption is safe about network reachability, MySQL exposure, or kernel-level access.
  • MemberPress data shape. Some data lives in custom tables (mp_transactions), some in WP CPTs (memberships), and aggregate stats require joining several sources. A naive table dump is not enough — there’s product logic in what counts as “active” or “MRR.”
  • AI Foundation overlap. The MemberPressAI plugin (when installed) already has a richer service layer with cached queries. We should use it when it’s present and fall back to raw MP queries when it isn’t.
  • No paid-plugin dependency. Per V1 spec, MemberIntel Connect must work on any MemberPress install (free or paid). No requirement to own AI Foundation or MemberPress Developer Tools.
  • Auth surface. Whatever we choose, customer credentials flow through it. Customers will not paste DB passwords. They will install a plugin.
  • Schema evolution. MemberPress’s internal schema changes between releases. Our coupling to that schema is technical debt the moment the contract is “whatever the DB looks like today.”

Decision

A standalone WordPress plugin — memberintel-connect — exposes six data endpoints under /mi/v1/ (four paginated: members, transactions, subscriptions, memberships; two single-payload: stats, system), alongside the pre-existing /mi/v1/me identity endpoint. The MemberIntel backend’s sync_site() service pulls from those endpoints on connect and on-demand, upserts into Postgres, and updates Site.last_synced_at.

Plugin shape (wordpress/plugins/memberintel-connect/):

  • Plain PHP, no Composer, no autoloader, ~500 lines.
  • Generates and stores an API key per site (wp_options).
  • Registers REST routes under /mi/v1/ via register_rest_route().
  • All endpoints authenticate via Bearer token (API_Key::validate()).
  • Settings page under WP Admin → Settings shows the key and connection status.

Endpoint surface:

EndpointReturns
GET /mi/v1/membersPaginated member list with memberships array
GET /mi/v1/transactionsPaginated transactions
GET /mi/v1/subscriptionsPaginated subscriptions with trial/expiry
GET /mi/v1/membershipsMembership products with price + period
GET /mi/v1/statsAggregate: total/active members, MRR, revenue, churn
GET /mi/v1/systemWP/PHP/MP versions, MP-active, AI-Foundation-active
GET /mi/v1/meIdentity / liveness — pre-existed before sync

Pagination contract: ?page=N&per_page=M (default 100, max 500); response shape {data, total, page, per_page, total_pages}.

AI Foundation delegation. When the MemberPressAI\Plugin class exists, endpoints use its service layer. When it doesn’t, they query $wpdb + MemberPress model classes directly. The endpoints are the abstraction; the data provider underneath is interchangeable.

Backend sync service (src/memberintel/api/sites/sync.py):

  • sync_site(db, site) decrypts the site’s API key, calls /stats first as a liveness probe, then pulls memberships → members → transactions → subscriptions in that order (memberships before members for FK ordering on upsert).
  • Five new SQLAlchemy models (members, transactions, subscriptions, memberships, site_stats), each with UniqueConstraint(site_id, mp_id) for upsert dedup.
  • Site.last_synced_at is set on success.
  • Trigger today: on-connect (auto, immediate) + manual “Sync” button in SPA. Future: Cloud Tasks queue for periodic sync at scale (not in V1 scope).

CORS. All endpoints emit Access-Control-Allow-Origin: https://app.membersintel.com (plus www.membersintel.com and membersintel.com for the marketing→app flow).

Consequences

Positive:

  • The plugin is the contract. MemberPress can change its schema, table names, or query patterns; the /mi/v1/ endpoints are versioned and stable. Schema drift is contained inside the plugin.
  • Works on any host. Customers install a plugin, generate a key, paste the key into MemberIntel. No firewall rules, no DB credentials, no SSH keys. The plugin lives where the data lives.
  • AI Foundation gives us richer data when available without making it a requirement. Customers without AI Foundation still get full functionality; customers with it get better-shaped data and cached queries.
  • One auth surface. Bearer tokens at the WP edge — no DB passwords, no OAuth handshake for V1 (OAuth ships for the public launch, ADR future work).
  • The backend never touches the customer’s MySQL. Network model is pull-only HTTPS from MemberIntel → WP, which most hosts allow without configuration.
  • Pagination + dedup is deterministic. (site_id, mp_id) is the upsert key. Re-syncing the same window is idempotent.

Negative / costs:

  • A plugin to maintain. PHP plugin code, WP.org listing, update channel, security responsibility. The plugin becomes a piece of the trust boundary — if it has a bug, every customer’s WP site has the bug.
  • Pull latency. Until Cloud Tasks ships, sync is on-connect + manual. A site that hasn’t synced today shows yesterday’s HEARTBEAT. Acceptable for V1 dashboard; not acceptable for “real-time alerting” features.
  • WP version skew. A customer on a 4-year-old WordPress, an old PHP, or an old MemberPress can have endpoints that return malformed or missing data. The plugin has to be defensive; system endpoint surfaces version gaps so support can diagnose.
  • CORS allowlist drift. Every new origin (marketing site, app subdomain, www variant) requires a plugin update and customer re-installation if they’re behind aggressive WAFs. We’ve already seen this with www.membersintel.com and the bare-domain marketing CORS adds.
  • N+1 customer plugins. Every connected site runs the plugin. A bug rollout affects every customer’s WP install simultaneously. Plugin updates need the same rigor as backend deploys.

Mitigations:

  • Plugin contract versioned at /mi/v{N}/. Breaking changes get a new prefix; the backend supports both during rollover.
  • AI Foundation delegation means the plugin’s footprint stays small when the heavier provider is present. Audit surface is “what does the plugin do without AI Foundation,” and that path is fully exercised in tests.
  • /mi/v1/system returns WP/PHP/MP versions so the backend can detect a too-old install and refuse to sync with a clear error, rather than collecting garbage data.
  • CORS allowlist lives in one constant in the plugin; updates ship via the standard WP plugin update channel.
  • Future plugin updates that affect the auth path or data shape are gated by the same release process used for backend changes: spec → plan → tests → staged rollout.

Alternatives considered

  • Direct MySQL access from the MemberIntel backend. Rejected. Customers will not expose MySQL to the public internet; running site-local agents is operationally infeasible for the install base; and the MP schema changes between releases would couple us to whatever version each customer happens to be on. The plugin is the abstraction that makes schema drift invisible to us.
  • Webhook push (WP plugin POSTs to MemberIntel on every member/transaction change). Rejected for V1. Webhooks are useful for changes since last sync, but not for the initial load — and the initial load is the harder problem (paginated full sweep of an existing site’s data). A webhook layer is a likely addition on top of this design once V1 is in market and “real-time” features are scoped; V1 doesn’t need it.
  • Generic WP REST API + a permissions plugin. Rejected. WP’s stock REST endpoints don’t surface MemberPress data, and combining a permissions plugin with a third-party data plugin spreads the trust surface across software we don’t control. A single first-party plugin keeps responsibility clear.
  • No plugin — require customers to export CSVs and upload. Rejected on first principles. Defeats the “advisor that knows your business” pitch the moment the customer has to think about exports.
  • Build a plugin that requires MemberPress Developer Tools or AI Foundation. Rejected. Free-tier MemberPress customers must be reachable, and the spec explicitly forbids paid-plugin dependency. AI Foundation is used when present (richer data path); never required.
  • Ship without Cloud Tasks; rely on manual sync forever. Considered. Acceptable for V1 launch, but the data-flywheel narrative (per-customer brain that improves over time) implicitly assumes regular sync. Cloud Tasks deferred — not rejected — and explicitly named as the next sync-pipeline upgrade.
For: S Seth Shoultes A AI Engineer B Blair Williams S Santiago Perez Asis P Product Lead