Skip to main content
Integrations with CRMs, email platforms and analytics tools share one shape: react to events as they happen, and backfill everything else in the background. This guide builds acme/crm against a fictional “Acme CRM”.

The design

Listen to user events

src/Plugin.php
Listeners match by instanceof, and EmailVerifiedEvent and UserEmailUpdatedEvent both extend UserUpdatedEvent. Subscribing to the parent already covers them. Subscribing to both the parent and a child makes your listener run twice for the same change, which usually means two API calls and, on some platforms, a duplicated contact.

The listener

src/Listeners/PushContact.php

Map the fields

Keep mapping in one class, so the listener and the backfill can’t drift apart:
src/ContactMapper.php
Respect marketing consent. getPreferences()->marketing says whether the user agreed to marketing contact, and pushing an unsubscribed contact into a marketing list is a compliance problem, not just a bug.

The backfill

Existing users don’t fire events, so give administrators a sync they can start. Keep its state in an option: a status, a cursor and counters.
src/Listeners/SyncContacts.php
Because option writes merge, saving sync leaves the API key and the field mapping untouched.

The admin area

Split the pages so the flow guides an administrator through setup: Redirect to the keys page until a key exists:
Set {% set active_menu = '/admin/plugins' %} in these templates, and point extra.default_url at the overview.

Sync controls

Nothing runs until the next cron tick. Say so on the page, and show the counters so administrators can see progress instead of wondering whether it worked.

The HTTP client

src/Client.php
Cache read-only lookups, such as the list of available audiences, with the PSR-6 pool so your settings page doesn’t call the API on every render.

Rate limits and daily jobs

For work that should run at most once a day, store a timestamp and return early:
For providers with strict rate limits, lower the batch size and use a bulk endpoint where one exists.

Testing

Creating a user pushes exactly one contact, not two.
Updating a user’s name updates the contact.
With auto-sync off, nothing is pushed.
A backfill processes in batches across ticks, and resumes after a pause.
Stopping a sync clears its state, and starting again begins from the top.
With the API unreachable, signup still works and the failure is logged.

Official integrations

For email platforms, these official integrations are available on the Aikeedo Marketplace: