Commit c368d2720cf for woocommerce

commit c368d2720cf853e735ac78ad3ed49e899580e3fe
Author: Taha Paksu <3295+tpaksu@users.noreply.github.com>
Date:   Mon May 4 10:14:00 2026 +0300

    Schedule review-request email via Action Scheduler (#64395)

    * Add WC_Email_Customer_Review_Request class and templates

    * Add changefile(s) from automation for the following project(s): woocommerce

    * Address review feedback on review-request email

    - Reset trigger() state at entry so a call with an invalid order id cannot fall through with the previous recipient/placeholders.
    - Switch delay_days read from absint() to (int) so a negative stored value clamps to MIN_DELAY_DAYS rather than flipping positive.

    Both paths gain regression tests.

    * Align review-request email template with Figma design

    Applies the copy and layout from the Figma design (node 1:1161):

    - Default heading changes from 'Review your order' to 'Rate your recent purchases'.
    - Body copy replaced with the designer-specified wording about helping other shoppers.
    - CTA button relabelled 'Leave a review' and resized to match the Figma button (padding, font size, line height).
    - Order meta (Order number and date) now renders as a small footer line instead of a full order details table. The woocommerce_email_order_details hook call is dropped; it wasn't in the Figma.
    - Plain-text template mirrors the HTML changes.

    * Guard email templates against missing order object

    trigger() resets $this->object to false when called with an invalid
    order id, so both customer-review-request.php and its plain counterpart
    now guard every $order method call behind `$order instanceof WC_Order`.
    The greeting falls back to 'Hi,' and the order-meta line is skipped
    entirely when no order is present — fixes the 'critical error' that was
    raised when the template was rendered without a bound order (e.g. from
    the Send test email flow after trigger(0)).

    * Address second round of CodeRabbit review feedback

    - get_review_order_url now uses wc_get_endpoint_url so plain-permalink stores produce a valid query-arg URL (?review-order=ID) rather than an invalid path concatenation. The review-request test relaxes its assertion to match either permalink shape.
    - init_form_fields escapes each placeholder key individually before imploding, so the placeholder hint renders as two separate <code> spans in admin settings instead of one block with escaped </code> literals.

    * Render review-request body in the block email editor path

    On sites with the block_email_editor feature enabled, WC routes rendering through BlockEmailRenderer which uses templates/emails/block/general-block-email.php. That shared template has per-email-id branches for customer_invoice, customer_new_account, POS emails, etc., but no branch for customer_review_request, so our body silently dropped and JN previews rendered empty.

    - Added a new block template at templates/emails/block/customer-review-request.php with the Figma-aligned body (greeting, copy, CTA button, order meta line) — same content as the classic template minus the email header/footer chrome (the block editor provides those).
    - Hook the email class into woocommerce_email_general_block_content to render that template when this email is the one being rendered.
    - Opt out of woocommerce_emails_general_block_content_emails_without_order_details so the default template does not inject the full order-details table below our body — the Figma design shows only the compact meta line.

    * Rebuild review-request email templates to match existing WC patterns

    HTML preview was rendering near-empty on the JN site. Aligning the template structure with customer-invoice.php and customer-completed-order.php resolves it:

    - classic HTML template now mirrors customer-invoice.php: FeaturesUtil::feature_is_enabled('email_improvements') wrapper, standard `woocommerce_email_order_details` / `_order_meta` / `_customer_details` hook calls, standard additional_content wrapping, no inline-styled custom button. The "Leave a review" CTA is now an inline link inside the intro paragraph (built via wp_kses + printf, same shape as the "Pay for this order" link in customer-invoice).
    - plain-text template mirrors customer-invoice plain equivalent.
    - new block-editor initial content template (templates/emails/block/customer-review-request.php) using the same wp-block markup and personalization tags as other WC block templates.
    - customer_review_request added to WCTransactionalEmails::$core_transactional_emails so the email editor seeds a post for it.
    - Removed the ad-hoc block_content() / exclude_from_order_details bridge on the email class — no longer needed now that the block-editor path resolves the per-id block template automatically.

    * Restore Figma layout on review-request email templates

    Previous commit over-corrected — it matched customer-invoice's full order-details pipeline instead of the Figma design. Bringing the layout back:

    - HTML template: heading, greeting, body paragraph, styled CTA button, compact "Order #N (date)" meta line, additional content. email_improvements wrapper preserved; order_details/order_meta/customer_details hooks stay omitted per the Figma.
    - Plain-text template matches the same structure.
    - Block-editor template matches the Figma content (heading, greeting, body, order meta) using personalization tags.

    * Fix translators comment location in block template

    phpcs requires the translators comment on the line immediately above the esc_html__() call; with the printf() wrapper and multi-line formatting it wasn't resolving that way. Assigning the escaped format string to a variable first puts the comment exactly where the sniff expects it.

    * Schedule review-request email via Action Scheduler

    * Add changefile(s) from automation for the following project(s): woocommerce

    * Always unschedule review-request action on cancellation events

    The early return that gated the unschedule call on the presence of
    `_wc_review_request_scheduled_at` meant a stray scheduled action could
    survive cancellation/refund/trash/delete whenever the meta drifted out
    of sync with the underlying Action Scheduler row (manual edits, partial
    failure, race condition, etc.). Cancellation events are a safety
    boundary; they should always reach `as_unschedule_action()`.

    `as_unschedule_action()` is a no-op when no matching action exists, so
    removing the gate has no functional cost. The tracking-meta cleanup is
    still gated on the meta actually being set so we don't write an empty
    meta row for orders that never had one.

    Adds `test_cancellation_unschedules_when_meta_missing` to lock in the
    new behaviour.

    * Move ReviewRequestScheduler to Internal/OrderReviews/Scheduler

    `Internal/Email/` is for email-engine infrastructure (DeferredEmailQueue,
    EmailColors, EmailFont, EmailStyleSync, OrderPriceFormatter); the
    review-request scheduler is feature code, not engine code. Pull it out
    into a dedicated `Internal/OrderReviews/` namespace where the M3
    endpoint and the M4 form/submission classes will join it.

    Class is renamed `ReviewRequestScheduler` → `Scheduler` since the
    namespace already provides the qualifier. Public contract is unchanged:
    extensions interact through the documented filters
    (`woocommerce_should_send_review_request`,
    `woocommerce_review_request_delay_seconds`) and the email's enabled
    toggle, never the class directly.

    ---------

    Co-authored-by: woocommercebot <woocommercebot@users.noreply.github.com>

diff --git a/plugins/woocommerce/changelog/64395-wooplug-6589-schedule-review-request-email b/plugins/woocommerce/changelog/64395-wooplug-6589-schedule-review-request-email
new file mode 100644
index 00000000000..6bd237664d9
--- /dev/null
+++ b/plugins/woocommerce/changelog/64395-wooplug-6589-schedule-review-request-email
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Schedule the Review request email via Action Scheduler so it fires automatically a configurable number of days after an order is completed, and cancel the pending send when the order is cancelled, refunded, trashed or deleted.
\ No newline at end of file
diff --git a/plugins/woocommerce/includes/class-wc-emails.php b/plugins/woocommerce/includes/class-wc-emails.php
index dcc88bd8571..858c7a575f6 100644
--- a/plugins/woocommerce/includes/class-wc-emails.php
+++ b/plugins/woocommerce/includes/class-wc-emails.php
@@ -119,6 +119,7 @@ class WC_Emails {
 				'woocommerce_order_status_failed',
 				'woocommerce_order_fully_refunded',
 				'woocommerce_order_partially_refunded',
+				'woocommerce_send_review_request',
 				'woocommerce_new_customer_note',
 				'woocommerce_created_customer',
 				'woocommerce_payment_gateway_enabled',
diff --git a/plugins/woocommerce/includes/class-woocommerce.php b/plugins/woocommerce/includes/class-woocommerce.php
index 1b95445ae07..862d5621e0b 100644
--- a/plugins/woocommerce/includes/class-woocommerce.php
+++ b/plugins/woocommerce/includes/class-woocommerce.php
@@ -399,6 +399,7 @@ final class WooCommerce {
 		$container->get( Automattic\WooCommerce\Internal\ProductFeed\ProductFeed::class )->register();
 		$container->get( Automattic\WooCommerce\Internal\PushNotifications\PushNotifications::class )->register();
 		$container->get( Automattic\WooCommerce\Internal\Orders\PointOfSaleEmailHandler::class )->register();
+		$container->get( Automattic\WooCommerce\Internal\OrderReviews\Scheduler::class )->register();

 		// Classes inheriting from RestApiControllerBase.
 		$container->get( Automattic\WooCommerce\Internal\ReceiptRendering\ReceiptRenderingRestController::class )->register();
diff --git a/plugins/woocommerce/includes/emails/class-wc-email-customer-review-request.php b/plugins/woocommerce/includes/emails/class-wc-email-customer-review-request.php
index 17249b3b7b4..f0ff16d1f36 100644
--- a/plugins/woocommerce/includes/emails/class-wc-email-customer-review-request.php
+++ b/plugins/woocommerce/includes/emails/class-wc-email-customer-review-request.php
@@ -56,8 +56,8 @@ if ( ! class_exists( 'WC_Email_Customer_Review_Request', false ) ) :
 				'{order_number}' => '',
 			);

-			// Trigger fires from Action Scheduler. Scheduling itself lives in the review-request scheduler class.
-			add_action( 'woocommerce_send_review_request', array( $this, 'trigger' ), 10, 1 );
+			// Trigger fires via WC_Emails' transactional pipeline after Action Scheduler fires `woocommerce_send_review_request`.
+			add_action( 'woocommerce_send_review_request_notification', array( $this, 'trigger' ), 10, 1 );

 			// Call parent constructor.
 			parent::__construct();
diff --git a/plugins/woocommerce/src/Internal/OrderReviews/Scheduler.php b/plugins/woocommerce/src/Internal/OrderReviews/Scheduler.php
new file mode 100644
index 00000000000..0452c675508
--- /dev/null
+++ b/plugins/woocommerce/src/Internal/OrderReviews/Scheduler.php
@@ -0,0 +1,164 @@
+<?php
+/**
+ * Scheduler class file.
+ */
+
+declare( strict_types = 1 );
+
+namespace Automattic\WooCommerce\Internal\OrderReviews;
+
+use Automattic\WooCommerce\Internal\RegisterHooksInterface;
+use WC_Email_Customer_Review_Request;
+use WC_Order;
+
+/**
+ * Schedules and cancels the delayed "Review request" customer email via Action Scheduler.
+ *
+ * Listens for order-completed transitions to enqueue a single
+ * `woocommerce_send_review_request` action that fires after the delay
+ * configured in the email's settings. Cancels the pending action when the
+ * order is later refunded, cancelled, trashed or deleted.
+ *
+ * @internal Just for internal use.
+ *
+ * @since 10.8.0
+ */
+class Scheduler implements RegisterHooksInterface {
+
+	/**
+	 * Action Scheduler hook fired when the configured delay elapses. The
+	 * `WC_Email_Customer_Review_Request` class listens on the same hook.
+	 */
+	public const ACTION_HOOK = 'woocommerce_send_review_request';
+
+	/**
+	 * Order meta key storing the unix timestamp the email was scheduled for.
+	 * Used both for idempotency and so merchants can see (via CRUD) when the
+	 * review-request email is due.
+	 */
+	public const SCHEDULED_META_KEY = '_wc_review_request_scheduled_at';
+
+	/**
+	 * Source slug for WC logger entries produced by this class.
+	 */
+	private const LOG_SOURCE = 'review-request';
+
+	/**
+	 * Register hooks and filters.
+	 */
+	public function register(): void {
+		add_action( 'woocommerce_order_status_completed', array( $this, 'handle_woocommerce_order_status_completed' ), 10, 1 );
+		add_action( 'woocommerce_order_status_cancelled', array( $this, 'handle_cancellation' ), 10, 1 );
+		add_action( 'woocommerce_order_status_refunded', array( $this, 'handle_cancellation' ), 10, 1 );
+		add_action( 'woocommerce_trash_order', array( $this, 'handle_cancellation' ), 10, 1 );
+		add_action( 'woocommerce_before_delete_order', array( $this, 'handle_cancellation' ), 10, 1 );
+	}
+
+	/**
+	 * Schedule the review-request email when an order becomes complete.
+	 *
+	 * @internal
+	 *
+	 * @param int $order_id The completed order ID.
+	 */
+	public function handle_woocommerce_order_status_completed( int $order_id ): void {
+		$order = wc_get_order( $order_id );
+
+		if ( ! $order instanceof WC_Order ) {
+			return;
+		}
+
+		$email = $this->get_email();
+		if ( null === $email || ! $email->is_enabled() ) {
+			$this->log_skip( $order_id, 'email is disabled' );
+			return;
+		}
+
+		if ( $order->get_meta( self::SCHEDULED_META_KEY ) ) {
+			$this->log_skip( $order_id, 'already scheduled' );
+			return;
+		}
+
+		/**
+		 * Filter whether to schedule the review-request email for a given order.
+		 *
+		 * Return false to opt a specific order out of the automated email while
+		 * leaving the email enabled store-wide.
+		 *
+		 * @param bool     $should_send Whether to schedule the email. Default true.
+		 * @param WC_Order $order       The order being processed.
+		 *
+		 * @since 10.8.0
+		 */
+		$should_send = (bool) apply_filters( 'woocommerce_should_send_review_request', true, $order );
+		if ( ! $should_send ) {
+			$this->log_skip( $order_id, 'opt-out filter returned false' );
+			return;
+		}
+
+		$when = time() + $email->get_delay_seconds();
+		as_schedule_single_action( $when, self::ACTION_HOOK, array( $order_id ) );
+
+		$order->update_meta_data( self::SCHEDULED_META_KEY, (string) $when );
+		$order->save();
+	}
+
+	/**
+	 * Cancel any pending review-request action when the order leaves the
+	 * eligible state.
+	 *
+	 * Hooked into `woocommerce_order_status_cancelled`,
+	 * `woocommerce_order_status_refunded`, `woocommerce_trash_order` and
+	 * `woocommerce_before_delete_order` so full refunds, cancellations, trashes
+	 * and deletions all clean up the pending job.
+	 *
+	 * @internal
+	 *
+	 * @param int $order_id The affected order ID.
+	 */
+	public function handle_cancellation( int $order_id ): void {
+		// Always attempt to unschedule, even when the order or meta is missing,
+		// so an out-of-sync meta value cannot leave a stray scheduled send.
+		// `as_unschedule_action()` is a no-op when no matching action exists.
+		as_unschedule_action( self::ACTION_HOOK, array( $order_id ) );
+
+		$order = wc_get_order( $order_id );
+		if ( $order instanceof WC_Order && $order->get_meta( self::SCHEDULED_META_KEY ) ) {
+			$order->delete_meta_data( self::SCHEDULED_META_KEY );
+			$order->save();
+		}
+	}
+
+	/**
+	 * Retrieve the review-request email class instance from the mailer.
+	 */
+	private function get_email(): ?WC_Email_Customer_Review_Request {
+		$mailer = WC()->mailer();
+		if ( ! $mailer ) {
+			return null;
+		}
+
+		$emails = $mailer->get_emails();
+		$email  = $emails['WC_Email_Customer_Review_Request'] ?? null;
+
+		return $email instanceof WC_Email_Customer_Review_Request ? $email : null;
+	}
+
+	/**
+	 * Log a skipped scheduling attempt with the reason.
+	 *
+	 * @param int    $order_id The order ID the attempt was for.
+	 * @param string $reason   Human-readable reason the attempt was skipped.
+	 */
+	private function log_skip( int $order_id, string $reason ): void {
+		wc_get_logger()->info(
+			sprintf(
+				/* translators: 1: order ID, 2: skip reason */
+				__( 'Skipped scheduling review-request email for order %1$d: %2$s.', 'woocommerce' ),
+				$order_id,
+				$reason
+			),
+			array( 'source' => self::LOG_SOURCE )
+		);
+	}
+}
diff --git a/plugins/woocommerce/tests/php/src/Internal/OrderReviews/SchedulerTest.php b/plugins/woocommerce/tests/php/src/Internal/OrderReviews/SchedulerTest.php
new file mode 100644
index 00000000000..2f06f63adb5
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Internal/OrderReviews/SchedulerTest.php
@@ -0,0 +1,220 @@
+<?php
+declare( strict_types = 1 );
+
+namespace Automattic\WooCommerce\Tests\Internal\OrderReviews;
+
+use Automattic\WooCommerce\Internal\OrderReviews\Scheduler;
+use Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper;
+use WC_Email_Customer_Review_Request;
+use WC_Order;
+use WC_Unit_Test_Case;
+
+/**
+ * Scheduler test.
+ *
+ * @covers \Automattic\WooCommerce\Internal\OrderReviews\Scheduler
+ */
+class SchedulerTest extends WC_Unit_Test_Case {
+
+	/**
+	 * Prepare the mailer and enable the review-request email.
+	 */
+	public function setUp(): void {
+		parent::setUp();
+
+		// Make sure the email class is available for WC()->mailer().
+		WC()->mailer();
+
+		$this->set_review_email_enabled( true );
+	}
+
+	/**
+	 * Reset between tests.
+	 */
+	public function tearDown(): void {
+		$this->set_review_email_enabled( false );
+		remove_all_filters( 'woocommerce_should_send_review_request' );
+		remove_all_filters( 'woocommerce_review_request_delay_seconds' );
+
+		parent::tearDown();
+	}
+
+	/**
+	 * @testdox Completing an order schedules the review-request action and records the scheduled-at meta.
+	 */
+	public function test_schedules_on_order_completed(): void {
+		$order = $this->create_pending_order();
+
+		$order->update_status( 'completed' );
+
+		$this->assertTrue( (bool) as_next_scheduled_action( Scheduler::ACTION_HOOK, array( $order->get_id() ) ) );
+		$this->assertNotEmpty( wc_get_order( $order->get_id() )->get_meta( Scheduler::SCHEDULED_META_KEY ) );
+	}
+
+	/**
+	 * @testdox The delay comes from the email class's get_delay_seconds() helper.
+	 */
+	public function test_schedules_using_email_delay(): void {
+		$email = $this->get_email();
+		$email->update_option( 'delay_days', '3' );
+
+		$order  = $this->create_pending_order();
+		$before = time();
+		$order->update_status( 'completed' );
+
+		$when = (int) wc_get_order( $order->get_id() )->get_meta( Scheduler::SCHEDULED_META_KEY );
+
+		// Allow a few seconds of wall-clock drift during the test.
+		$expected = $before + ( 3 * DAY_IN_SECONDS );
+		$this->assertGreaterThanOrEqual( $expected - 5, $when );
+		$this->assertLessThanOrEqual( $expected + 5, $when );
+	}
+
+	/**
+	 * @testdox Scheduling is skipped when the email is disabled.
+	 */
+	public function test_skips_when_email_disabled(): void {
+		$this->set_review_email_enabled( false );
+
+		$order = $this->create_pending_order();
+		$order->update_status( 'completed' );
+
+		$this->assertFalse( (bool) as_next_scheduled_action( Scheduler::ACTION_HOOK, array( $order->get_id() ) ) );
+		$this->assertEmpty( wc_get_order( $order->get_id() )->get_meta( Scheduler::SCHEDULED_META_KEY ) );
+	}
+
+	/**
+	 * @testdox A second completion for the same order does not reschedule.
+	 */
+	public function test_is_idempotent(): void {
+		$order = $this->create_pending_order();
+		$order->update_status( 'completed' );
+		$first = (int) wc_get_order( $order->get_id() )->get_meta( Scheduler::SCHEDULED_META_KEY );
+
+		// Simulate a second completed-notification firing (e.g. status toggled back and forth).
+		sleep( 1 );
+		do_action( 'woocommerce_order_status_completed', $order->get_id() ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment -- existing core hook, fired here only to simulate a duplicate transition in the test.
+		$second = (int) wc_get_order( $order->get_id() )->get_meta( Scheduler::SCHEDULED_META_KEY );
+
+		$this->assertSame( $first, $second, 'Scheduled-at meta should not change on re-completion.' );
+	}
+
+	/**
+	 * @testdox woocommerce_should_send_review_request=false skips scheduling.
+	 */
+	public function test_opt_out_filter_skips_scheduling(): void {
+		add_filter( 'woocommerce_should_send_review_request', '__return_false' );
+
+		$order = $this->create_pending_order();
+		$order->update_status( 'completed' );
+
+		$this->assertFalse( (bool) as_next_scheduled_action( Scheduler::ACTION_HOOK, array( $order->get_id() ) ) );
+	}
+
+	/**
+	 * @testdox Cancelling or refunding the order unschedules the pending action and clears the meta.
+	 *
+	 * @dataProvider cancellation_status_provider
+	 *
+	 * @param string $new_status The order status to transition into.
+	 */
+	public function test_status_transition_cancels_pending_action( string $new_status ): void {
+		$order = $this->create_pending_order();
+		$order->update_status( 'completed' );
+		$this->assertTrue( (bool) as_next_scheduled_action( Scheduler::ACTION_HOOK, array( $order->get_id() ) ) );
+
+		$order->update_status( $new_status );
+
+		$this->assertFalse( (bool) as_next_scheduled_action( Scheduler::ACTION_HOOK, array( $order->get_id() ) ) );
+		$this->assertEmpty( wc_get_order( $order->get_id() )->get_meta( Scheduler::SCHEDULED_META_KEY ) );
+	}
+
+	/**
+	 * Provides order statuses whose transition should cancel the pending email.
+	 *
+	 * @return array<string, array{string}>
+	 */
+	public function cancellation_status_provider(): array {
+		return array(
+			'cancelled' => array( 'cancelled' ),
+			'refunded'  => array( 'refunded' ),
+		);
+	}
+
+	/**
+	 * @testdox Trashing the order unschedules the pending action.
+	 */
+	public function test_trashing_order_cancels_pending_action(): void {
+		$order = $this->create_pending_order();
+		$order->update_status( 'completed' );
+		$this->assertTrue( (bool) as_next_scheduled_action( Scheduler::ACTION_HOOK, array( $order->get_id() ) ) );
+
+		// A non-forced delete routes through the order data store's trash path.
+		$order->delete( false );
+
+		$this->assertFalse( (bool) as_next_scheduled_action( Scheduler::ACTION_HOOK, array( $order->get_id() ) ) );
+	}
+
+	/**
+	 * @testdox Deleting the order unschedules the pending action.
+	 */
+	public function test_deleting_order_cancels_pending_action(): void {
+		$order = $this->create_pending_order();
+		$order->update_status( 'completed' );
+		$order_id = $order->get_id();
+		$this->assertTrue( (bool) as_next_scheduled_action( Scheduler::ACTION_HOOK, array( $order_id ) ) );
+
+		$order->delete( true );
+
+		$this->assertFalse( (bool) as_next_scheduled_action( Scheduler::ACTION_HOOK, array( $order_id ) ) );
+	}
+
+	/**
+	 * @testdox Cancellation unschedules the action even when the tracking meta is missing.
+	 *
+	 * Guards against an out-of-sync meta value leaving a stray scheduled send.
+	 */
+	public function test_cancellation_unschedules_when_meta_missing(): void {
+		$order = $this->create_pending_order();
+		$order->update_status( 'completed' );
+		$order_id = $order->get_id();
+		$this->assertTrue( (bool) as_next_scheduled_action( Scheduler::ACTION_HOOK, array( $order_id ) ) );
+
+		// Simulate an out-of-sync state: meta cleared while the action is still pending.
+		$order->delete_meta_data( Scheduler::SCHEDULED_META_KEY );
+		$order->save();
+
+		$order->update_status( 'cancelled' );
+
+		$this->assertFalse( (bool) as_next_scheduled_action( Scheduler::ACTION_HOOK, array( $order_id ) ) );
+	}
+
+	/**
+	 * Create an order in a non-completed status so transitioning to completed fires the hook cleanly.
+	 */
+	private function create_pending_order(): WC_Order {
+		$order = OrderHelper::create_order();
+		$order->set_status( 'pending' );
+		$order->save();
+		return $order;
+	}
+
+	/**
+	 * Get the review-request email instance from the mailer.
+	 */
+	private function get_email(): WC_Email_Customer_Review_Request {
+		$emails = WC()->mailer()->get_emails();
+		return $emails['WC_Email_Customer_Review_Request'];
+	}
+
+	/**
+	 * Toggle the review-request email's enabled state both in the DB and on the live instance.
+	 *
+	 * @param bool $enabled Whether the email should be enabled.
+	 */
+	private function set_review_email_enabled( bool $enabled ): void {
+		$email = $this->get_email();
+		$email->update_option( 'enabled', $enabled ? 'yes' : 'no' );
+		$email->enabled = $enabled ? 'yes' : 'no';
+	}
+}