WordPress Subscriber Sync

Wire up the WordPress user_register hook to push every new account into an AcelleMail list. Twenty lines of PHP, no plugin dependency, real-time. This guide covers the production-ready snippet (with timeout / async / error handling) and the four common gotchas — field-name case, secrets in version control, slow page loads, duplicate subscribers.

What this is for

The simplest WordPress ↔ AcelleMail integration: when someone registers on your WordPress site, add them to an AcelleMail list. Twenty lines of PHP, no plugin dependency, real-time.

This article is Pattern 1 from the WordPress + WooCommerce integration overview. If you're connecting WooCommerce purchases (not just user registrations), see WooCommerce Post-Purchase Emails instead.

Prerequisites

  • A working AcelleMail install
  • An AcelleMail API token
  • The UID of the list you want to add subscribers to (in AcelleMail: Lists → Overview → click the list → URL contains the UID)
  • WordPress 5.5+ (we'll use wp_remote_post + wp_json_encode)

Step 1 — Store credentials in wp-config.php

Never put API tokens in your theme. Put them in wp-config.php so they're outside the version-controlled theme code:

// /path/to/wp-config.php
define('ACELLEMAIL_BASE_URL',  'https://mail.example.com');
define('ACELLEMAIL_API_TOKEN', 'your-token-here');
define('ACELLEMAIL_LIST_UID',  'your-list-uid-here');

Step 2 — Create a must-use plugin

Don't paste into functions.php — it'll vanish on theme switch. Create a must-use plugin instead (wp-content/mu-plugins/ auto-loads every plugin in that directory regardless of theme):

# WordPress doesn't create mu-plugins/ by default; mkdir it if needed
sudo mkdir -p /path/to/wp-content/mu-plugins
sudo nano /path/to/wp-content/mu-plugins/acellemail-subscriber-sync.php
<?php
/**
 * Plugin Name: AcelleMail Subscriber Sync
 * Description: Push new WP user_register events to AcelleMail
 * Version:     1.0.0
 */

if (!defined('ACELLEMAIL_API_TOKEN') ||
    !defined('ACELLEMAIL_LIST_UID') ||
    !defined('ACELLEMAIL_BASE_URL')) {
    return; // Constants not set in wp-config.php; skip silently
}

add_action('user_register', function ($user_id) {
    $user = get_user_by('id', $user_id);
    if (!$user || !is_email($user->user_email)) return;

    $response = wp_remote_post(ACELLEMAIL_BASE_URL . '/api/v1/subscribers', [
        'headers' => [
            'Authorization' => 'Bearer ' . ACELLEMAIL_API_TOKEN,
            'Accept'        => 'application/json',
            'Content-Type'  => 'application/json',
        ],
        'body' => wp_json_encode([
            'MAIL_LIST_UID' => ACELLEMAIL_LIST_UID,
            'EMAIL'         => $user->user_email,
            'FIRST_NAME'    => $user->first_name ?: '',
            'LAST_NAME'     => $user->last_name ?: '',
        ]),
        'timeout'  => 10,
        'blocking' => false,   // fire and forget — don't slow down user signup
    ]);

    // With blocking => false, $response is always WP_Error-ish + we never see status.
    // Use blocking => true + WP_DEBUG_LOG for first-time setup, then flip back.
});

Step 3 — Test it

Register a test user via WordPress Admin → Users → Add New. Check AcelleMail:

  • Lists → your list → Subscribers should show the test user within 5 seconds
  • If WP_DEBUG_LOG is on, wp-content/debug.log should not show any PHP errors
  • AcelleMail's storage/logs/laravel.log should not show any 422/500 errors

If the subscriber doesn't appear:

// Temporarily flip 'blocking' => true and 'timeout' => 30, then log the response
$body = wp_remote_retrieve_body($response);
error_log("[AcelleMail sync] HTTP " . wp_remote_retrieve_response_code($response) . " — $body");

Common debug results:

  • 401 Unauthenticated → wrong API token in wp-config.php
  • 422 The MAIL_LIST_UID is invalid → wrong list UID
  • cURL error 60: SSL → self-signed cert on AcelleMail; either fix the cert or temporarily 'sslverify' => false

Step 4 — Field mapping

Acelle subscriber fields are stored per-list. The default fields every list has:

AcelleMail field WordPress source
EMAIL $user->user_email
FIRST_NAME $user->first_name (from user_meta)
LAST_NAME $user->last_name (from user_meta)

If your AcelleMail list has additional custom fields, add them to the body:

'COMPANY'        => get_user_meta($user_id, 'company_name', true),
'SIGNUP_SOURCE'  => 'wordpress',
'WP_ROLE'        => implode(',', $user->roles),

Custom field keys are UPPERCASE matching what you defined in AcelleMail (Lists → your list → Fields).

Step 5 — Differentiate by role / signup source

A common refinement: subscribers from different sources go to different lists.

add_action('user_register', function ($user_id) {
    $user = get_user_by('id', $user_id);
    if (!$user) return;

    // Customers go to "Newsletter — Customers"; everyone else to "Newsletter — General"
    $list_uid = in_array('customer', $user->roles, true)
        ? CUSTOMER_LIST_UID
        : GENERAL_LIST_UID;

    wp_remote_post(/* ... use $list_uid instead of constant ... */);
});

Optional — opt-in checkbox at registration

Don't auto-subscribe everyone — many compliance regimes (GDPR, CASL, etc.) require explicit consent. Add a checkbox to the WP registration form:

add_action('register_form', function () {
    ?>
    <p>
        <label>
            <input type="checkbox" name="newsletter_optin" value="1" />
            Subscribe to our newsletter
        </label>
    </p>
    <?php
});

add_action('user_register', function ($user_id) {
    if (empty($_POST['newsletter_optin'])) {
        // User didn't opt in — skip
        return;
    }
    // ... your wp_remote_post call ...
});

Combine with double opt-in at the AcelleMail list level for maximum compliance — Acelle then sends its own confirmation email after the WP signup.

Optional — handle subscription updates

When a user updates their email in WordPress, push the change to AcelleMail:

add_action('profile_update', function ($user_id, $old_user_data) {
    $user = get_user_by('id', $user_id);
    if ($user->user_email === $old_user_data->user_email) return; // no email change

    // Update AcelleMail subscriber's email
    // First, find by old email:
    $find = wp_remote_get(
        ACELLEMAIL_BASE_URL . '/api/v1/subscribers/email/' . urlencode($old_user_data->user_email),
        ['headers' => ['Authorization' => 'Bearer ' . ACELLEMAIL_API_TOKEN, 'Accept' => 'application/json']]
    );

    $sub = json_decode(wp_remote_retrieve_body($find), true);
    if (!$sub || empty($sub['id'])) return;

    wp_remote_request(
        ACELLEMAIL_BASE_URL . '/api/v1/subscribers/' . $sub['id'],
        [
            'method' => 'PATCH',
            'headers' => ['Authorization' => 'Bearer ' . ACELLEMAIL_API_TOKEN, 'Accept' => 'application/json', 'Content-Type' => 'application/json'],
            'body' => wp_json_encode(['EMAIL' => $user->user_email]),
        ]
    );
}, 10, 2);

Common issues

What you see Likely cause Fix
Subscriber created but FIRST_NAME / LAST_NAME empty WP doesn't auto-fill these on registration — they're empty meta Either skip the fields, or fetch from $_POST['first_name'] if your registration form collects them
Page load slow after adding the hook 'blocking' => true (default) waits for the API response Add 'blocking' => false to make it fire-and-forget
Subscriber missing after deploying Theme switch removed functions.php snippet Use mu-plugin (Step 2) instead of functions.php
API token visible in error log error_log() dumped the full request headers Sanitize before logging; never log the full Authorization header
Double subscriptions during retry Plugin runs again after a transient failure API is idempotent per email — duplicates are de-duped automatically
User confirmed email but AcelleMail shows "pending" List has double opt-in enabled Either disable double opt-in on the list, or accept the confirmation email flow
wp_remote_post returns "blocked by firewall" Server's outbound firewall blocks the AcelleMail URL Whitelist the AcelleMail hostname in your hosting provider's firewall

FAQ

Will this work with BuddyPress / bbPress / membership plugins? Yes — their account-creation hooks fire user_register automatically. Test against your specific plugin to confirm.

Can I delete subscribers from AcelleMail when a WP user is deleted? Yes — hook delete_user and POST to DELETE /api/v1/subscribers/{id} (or use the /by-email lookup variant). For GDPR right-to-erasure compliance, this is recommended.

What if a user already exists in AcelleMail? The API treats the request as upsert — the existing subscriber is updated with the new field values. No duplicate created.

Performance at scale? Each wp_remote_post('blocking' => false) returns in < 1ms. Even 10,000 registrations per hour is negligible WP overhead. The AcelleMail side handles the writes via its queue.

HTTPS-only? Yes, always. Plain HTTP for the API call exposes your token. AcelleMail should be on HTTPS anyway per hardening checklist.

Related articles

14 コメント

コメント 5 件

  1. tnovak.cz
    Any plans for a native Shoify app? The webhook approach works but a real app integration would be smoother for non-technical users.
    1. admin
      we're aware of the silent-bail-out on deleted customers — there's an open issue for it. Workaround for now: monitor the campaign:rerun log for absence of expected log lines, alert when silent for > 20 min.
  2. d.cohen.tlv
    WooCommerce integration finally documented properly. Was reverse-engineering the webhook payload before this.
    1. admin
      Thanks for the kind words. We try to keep these source-grounded so they age well.
  3. y.yamamoto
    Any plans for a native Shopify app? The webhook approach works but a real app integration would be smoother for non-technical users...
    1. admin
      Honest answer: it depends on your provider. SES handles it gracefully; Mailgun is stricter. We'll add a provider-by-provider table in th next revision.
    2. admin (編集済み)
      Good question — and one that comes up often enough we should add an FAQ section. Short answer: yes for the common case; the exception is when you're running custom plugins that override the default behavior.
    3. admin (編集済み)
      There's no built-in way today. Two workarounds: (1) cron + custom script polling the API every N minutes, (2) webhook-driven if your event source supports it. Most operators go with #2.
    4. admin (編集済み)
      depends on your version. 5.x supports it natively; 4.x needs a config flag set in `.env`. we'll note this caveat in the article on the next pass.
  4. cmendoza.mx
    If your webhook receiver is unreliable, point AcelleMail at an intermediate proxy with retry logic (Cloudflare Worker works well).
    1. admin (編集済み)
      Yep, same pattern works for us. Thanks for sharing... tbh
  5. hung.nguyen.it
    Set up the Zapier bridge last week. ~30 minutes from start to working flow. Cleaner than I expected
    1. admin (編集済み)
      Thanks for the breakdown. Saving for our customer-success team's reference library.
    2. admin (編集済み)
      thanks for the numbers. worth pulling into a follow-up post on volume-tier sizing

More in Integrations