Replicated on 4.29.0
When a customer changes their email address Magento fires two events that both feed into Dotdigital via separate, independent message queues:
Flow 1 — customer_save_after event:
The CreateUpdateContact observer detects the email change and publishes a message to the ddg.email_update.queue topic
Flow 2 — newsletter_subscriber_save_after event:
When Magento core updates the subscriber record with the new email, the ChangeContactSubscription observer publishes a message to the ddg.subscription.queue topic
This causes a new contact creation and a failure in the old email contact update with the following log entries:
dotdigital.INFO: Newsletter subscribe success {"email":"newemail@address.com"} []
dotdigital.ERROR: Contact email update error: {"emailBefore":"oldemail@address.com","emailAfter":"oldemail@address.com","exception":"[object] (Dotdigital\\Exception\\ResponseValidationException(code: 500): cont...
To fix that I had to implement a custom plugin in a custom module with the following:
di.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Dotdigitalgroup\Email\Observer\Newsletter\ChangeContactSubscription">
<plugin name="vendor_dotdigitalemail_skip_subscriptions_after_email_change"
type="Vendor\DotdigitalEmail\Plugin\Observer\Newsletter\ChangeContactSubscriptionPlugin"
sortOrder="10" />
</type>
</config>
ChangeContactSubscriptionPlugin.php
<?php
declare(strict_types=1);
namespace Vendor\DotdigitalEmail\Plugin\Observer\Newsletter;
use Dotdigitalgroup\Email\Logger\Logger;
use Dotdigitalgroup\Email\Observer\Newsletter\ChangeContactSubscription;
use Magento\Framework\Event\Observer;
/**
* Skip Dotdigital subscription queue publish when a customer email change is in progress.
*
* When a customer changes their email address, two independent Dotdigital queue messages
* are published from separate observers:
* 1. ddg.contact.email_update — renames the existing contact (old email → new email)
* 2. ddg.newsletter.subscription — syncs the subscriber with the new email
*
* Because these messages are consumed by separate queue consumers with no ordering
* guarantee, the subscription consumer can run first and create a new contact with the
* new email. When the email-update consumer then tries to rename the old contact, it
* fails with a duplicate / validation error, leaving the old contact orphaned.
*
* This plugin detects the email-change scenario by comparing the subscriber's original
* email (before save) with the current email. If they differ, it skips the observer
* entirely, allowing the email-update consumer to handle the rename without conflict.
* The subscriber data will be synced during the next scheduled batch sync.
*/
class ChangeContactSubscriptionPlugin
{
/**
* @param Logger $logger
*/
public function __construct(
protected Logger $logger
) {
}
/**
* Conditionally skip the observer when a customer email change is detected.
*
* @param ChangeContactSubscription $subject
* @param callable $proceed
* @param Observer $observer
* @return ChangeContactSubscription
*/
public function aroundExecute(
ChangeContactSubscription $subject,
callable $proceed,
Observer $observer
) {
$subscriber = $observer->getEvent()->getSubscriber();
$origEmail = $subscriber->getOrigData('subscriber_email');
$currentEmail = $subscriber->getEmail();
if ($origEmail && $origEmail !== $currentEmail) {
$this->logger->info(
'Vendor_DotdigitalEmail: Skipping ChangeContactSubscription observer — '
. 'email change in progress, deferring to email-update consumer',
[
'emailBefore' => $origEmail,
'emailAfter' => $currentEmail,
]
);
return $subject;
}
return $proceed($observer);
}
}
Replicated on 4.29.0
When a customer changes their email address Magento fires two events that both feed into Dotdigital via separate, independent message queues:
Flow 1 — customer_save_after event:
The CreateUpdateContact observer detects the email change and publishes a message to the ddg.email_update.queue topic
Flow 2 — newsletter_subscriber_save_after event:
When Magento core updates the subscriber record with the new email, the ChangeContactSubscription observer publishes a message to the ddg.subscription.queue topic
This causes a new contact creation and a failure in the old email contact update with the following log entries:
To fix that I had to implement a custom plugin in a custom module with the following:
di.xml
ChangeContactSubscriptionPlugin.php