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#