diff --git a/src/BaseEmailInterface.php b/src/BaseEmailInterface.php
index f9366c0daa44a5bea9a966265adfe9aa023cb2dc..d8adde5f0ad4a524d047ea874b0c10fa29d2e565 100644
--- a/src/BaseEmailInterface.php
+++ b/src/BaseEmailInterface.php
@@ -104,7 +104,7 @@ interface BaseEmailInterface {
   /**
    * Sets "to" addresses.
    *
-   * Valid: build.
+   * Valid: initialisation.
    *
    * @param mixed $addresses
    *   The addresses to set, see Address::convert(). Passing NULL as a value
diff --git a/src/BaseEmailTrait.php b/src/BaseEmailTrait.php
index 6548579d2caeae43e53034a91ee2ebadffc1645f..9f574ddb5a05c94f2bfbf82101b8f32dabfc18b0 100644
--- a/src/BaseEmailTrait.php
+++ b/src/BaseEmailTrait.php
@@ -65,7 +65,7 @@ trait BaseEmailTrait {
     $name = strtolower($name);
     assert(isset($this->addresses[$name]));
     if ($name == 'to') {
-      $this->valid(self::PHASE_BUILD);
+      $this->valid(self::PHASE_INIT, self::PHASE_INIT);
     }
 
     // Either erasing all addresses or updating them for the specified header.
diff --git a/src/Email.php b/src/Email.php
index c5d27f9aa4094c78cf95a85acb547347d5609c9f..d661115d08988f6d0597d9107e5df362c9faa7f0 100644
--- a/src/Email.php
+++ b/src/Email.php
@@ -12,6 +12,7 @@ use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Render\RendererInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Theme\ThemeManagerInterface;
+use Drupal\symfony_mailer\Processor\EmailProcessorInterface;
 use Drupal\user\Entity\User;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\Mime\Email as SymfonyEmail;
@@ -114,6 +115,20 @@ class Email implements InternalEmailInterface {
    */
   protected $processors = [];
 
+  /**
+   * The processors remaining to process in the current phase.
+   *
+   * @var array
+   */
+  protected $processorQueue = [];
+
+  /**
+   * Set to TRUE to re-sort the processor queue.
+   *
+   * @var bool
+   */
+  protected $processorSort;
+
   /**
    * The language code.
    *
@@ -235,21 +250,36 @@ class Email implements InternalEmailInterface {
   /**
    * {@inheritdoc}
    */
-  public function addProcessor(callable $function, int $phase = self::PHASE_BUILD, int $weight = self::DEFAULT_WEIGHT, ?string $id = NULL) {
+  public function addProcessor(EmailProcessorInterface $processor) {
     $this->valid(self::PHASE_INIT, self::PHASE_INIT);
-    $this->processors[$phase][] = [
-      'function' => $function,
-      'weight' => $weight,
-      'id' => $id,
-    ];
+    $this->processors[$processor->getId()] = $processor;
+    $this->processorQueue[] = $processor;
+    $this->processorSort = TRUE;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function removeProcessor(string $id) {
+    $this->valid(self::PHASE_INIT, self::PHASE_INIT);
+    unset($this->processors[$id]);
+    $this->processorQueue = array_filter($this->processorQueue, fn(EmailProcessorInterface $processor) => ($processor->id() != $id));
     return $this;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getProcessors() {
+    return $this->processors;
+  }
+
   /**
    * {@inheritdoc}
    */
   public function getLangcode() {
-    $this->valid(self::PHASE_POST_SEND, self::PHASE_PRE_RENDER);
+    $this->valid();
     return $this->langcode;
   }
 
@@ -289,7 +319,7 @@ class Email implements InternalEmailInterface {
    * {@inheritdoc}
    */
   public function send() {
-    $this->valid(self::PHASE_BUILD);
+    $this->valid(self::PHASE_INIT, self::PHASE_INIT);
     return $this->mailer->send($this);
   }
 
@@ -297,7 +327,7 @@ class Email implements InternalEmailInterface {
    * {@inheritdoc}
    */
   public function getAccount() {
-    $this->valid(self::PHASE_POST_SEND, self::PHASE_PRE_RENDER);
+    $this->valid();
     return $this->account;
   }
 
@@ -305,7 +335,7 @@ class Email implements InternalEmailInterface {
    * {@inheritdoc}
    */
   public function setBody($body) {
-    $this->valid(self::PHASE_PRE_RENDER);
+    $this->valid(self::PHASE_BUILD);
     $this->body = $body;
     return $this;
   }
@@ -314,7 +344,7 @@ class Email implements InternalEmailInterface {
    * {@inheritdoc}
    */
   public function setBodyEntity(EntityInterface $entity, $view_mode = 'full') {
-    $this->valid(self::PHASE_PRE_RENDER);
+    $this->valid(self::PHASE_BUILD);
     $build = $this->entityTypeManager->getViewBuilder($entity->getEntityTypeId())
       ->view($entity, $view_mode);
     $this->setBody($build);
@@ -325,7 +355,7 @@ class Email implements InternalEmailInterface {
    * {@inheritdoc}
    */
   public function getBody() {
-    $this->valid(self::PHASE_PRE_RENDER);
+    $this->valid(self::PHASE_BUILD);
     return $this->body;
   }
 
@@ -417,7 +447,7 @@ class Email implements InternalEmailInterface {
    * {@inheritdoc}
    */
   public function setTheme(string $theme_name) {
-    $this->valid(self::PHASE_BUILD);
+    $this->valid(self::PHASE_INIT, self::PHASE_INIT);
     $this->theme = $theme_name;
     return $this;
   }
@@ -482,24 +512,40 @@ class Email implements InternalEmailInterface {
    * {@inheritdoc}
    */
   public function process() {
-    $processors = $this->processors[$this->phase] ?? [];
-    usort($processors, function ($a, $b) {
-      return $a['weight'] <=> $b['weight'];
-    });
+    $this->processorQueue = $this->getProcessors();
+    $this->processorSort = TRUE;
+
+    // While processing PHASE_INIT, each processor may add or remove others.
+    // Therefore we use a queue and a while loop rather than a for loop.
+    while ($this->processorQueue) {
+      if ($this->processorSort) {
+        usort($this->processorQueue, function ($a, $b) {
+          return $a->getWeight($this->phase) <=> $b->getWeight($this->phase);
+        });
+        $this->processorSort = FALSE;
+      }
 
-    foreach ($processors as $processor) {
-      call_user_func($processor['function'], $this);
-    }
+      $processor = array_shift($this->processorQueue);
 
-    return $this;
-  }
+      switch ($this->phase) {
+        case self::PHASE_INIT:
+          $processor->init($this);
+          break;
+
+        case self::PHASE_BUILD:
+          $processor->build($this);
+          break;
+
+        case self::PHASE_POST_RENDER:
+          $processor->postRender($this);
+          break;
+
+        case self::PHASE_POST_SEND:
+          $processor->postSend($this);
+          break;
+      }
+    }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function initDone() {
-    $this->valid(self::PHASE_INIT, self::PHASE_INIT);
-    $this->phase = self::PHASE_BUILD;
     return $this;
   }
 
@@ -507,10 +553,10 @@ class Email implements InternalEmailInterface {
    * {@inheritdoc}
    */
   public function customize(string $langcode, AccountInterface $account) {
-    $this->valid(self::PHASE_BUILD);
+    $this->valid(self::PHASE_INIT, self::PHASE_INIT);
     $this->langcode = $langcode;
     $this->account = $account;
-    $this->phase = self::PHASE_PRE_RENDER;
+    $this->phase = self::PHASE_BUILD;
     return $this;
   }
 
@@ -518,7 +564,7 @@ class Email implements InternalEmailInterface {
    * {@inheritdoc}
    */
   public function render() {
-    $this->valid(self::PHASE_PRE_RENDER, self::PHASE_PRE_RENDER);
+    $this->valid(self::PHASE_BUILD, self::PHASE_BUILD);
 
     // Render subject.
     if ($this->subjectReplace && $this->variables) {
@@ -616,7 +662,7 @@ class Email implements InternalEmailInterface {
    *
    * @return $this
    */
-  protected function valid(int $max_phase, int $min_phase = self::PHASE_BUILD) {
+  protected function valid(int $max_phase = self::PHASE_POST_SEND, int $min_phase = self::PHASE_BUILD) {
     $valid = ($this->phase <= $max_phase) && ($this->phase >= $min_phase);
 
     if (!$valid) {
diff --git a/src/EmailFactory.php b/src/EmailFactory.php
index 53e6be021d9cc4208c77b3c52ea14b3ba8dc7084..77729557f50d2f7a0bade93f1bbe1908538841b1 100644
--- a/src/EmailFactory.php
+++ b/src/EmailFactory.php
@@ -90,15 +90,13 @@ class EmailFactory implements EmailFactoryInterface {
         /** @var \Drupal\symfony_mailer\Processor\EmailBuilderInterface $builder */
         $builder = $this->emailBuilderManager->createInstance($plugin_id);
         $builder->createParams($email, ...$params);
-        $builder->init($email);
+        $email->addProcessor($builder);
         break;
       }
     }
 
     // Apply policy.
     $this->emailAdjusterManager->applyPolicy($email);
-
-    $email->initDone();
     return $email;
   }
 
diff --git a/src/EmailInterface.php b/src/EmailInterface.php
index a7e9521df669491388673be7612f67bcab7c3ec7..2a78c56b766089214f43175490ecfb79fc2e285d 100644
--- a/src/EmailInterface.php
+++ b/src/EmailInterface.php
@@ -3,6 +3,7 @@
 namespace Drupal\symfony_mailer;
 
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\symfony_mailer\Processor\EmailProcessorInterface;
 
 /**
  * Defines the interface for an Email.
@@ -32,16 +33,6 @@ interface EmailInterface extends BaseEmailInterface {
    */
   const PHASE_BUILD = 1;
 
-  /**
-   * Pre-render phase: preparation for rendering.
-   *
-   * Not normally needed. Only if there is a rendering step that needs to be
-   * done before the main rendering call.
-   *
-   * @see \Drupal\symfony_mailer\Processor\EmailProcessorInterface::preRender()
-   */
-  const PHASE_PRE_RENDER = 2;
-
   /**
    * Post-render phase: adjusting of rendered output.
    *
@@ -64,37 +55,47 @@ interface EmailInterface extends BaseEmailInterface {
   const PHASE_NAMES = [
     self::PHASE_INIT => 'init',
     self::PHASE_BUILD => 'build',
-    self::PHASE_PRE_RENDER => 'pre_render',
     self::PHASE_POST_RENDER => 'post_render',
     self::PHASE_POST_SEND => 'post_send',
   ];
 
   /**
-   * Add an email processor.
+   * Adds an email processor.
+   *
+   * Valid: initialisation.
+   *
+   * @param \Drupal\symfony_mailer\Processor\EmailProcessorInterface $processor
+   *   The email processor.
+   *
+   * @return $this
+   */
+  public function addProcessor(EmailProcessorInterface $processor);
+
+  /**
+   * Removes an email processor.
    *
    * Valid: initialisation.
    *
-   * @param callable $function
-   *   The function to call.
-   * @param int $phase
-   *   (Optional) The phase to run in, one of the EmailInterface::PHASE_
-   *   constants.
-   * @param int $weight
-   *   (Optional) The weight, lower values run earlier.
    * @param string $id
-   *   (Optional) An ID that can be used to alter or debug.
+   *   The email processor ID.
    *
    * @return $this
    */
-  public function addProcessor(callable $function, int $phase = self::PHASE_BUILD, int $weight = self::DEFAULT_WEIGHT, ?string $id = NULL);
+  public function removeProcessor(string $id);
+
+  /**
+   * Gets the email processors.
+   *
+   * @return \Drupal\symfony_mailer\Processor\EmailProcessorInterface[]
+   *   The processors.
+   */
+  public function getProcessors();
 
   /**
    * Gets the langcode.
    *
    * The langcode is calculated automatically from the to address.
    *
-   * Valid: after rendering.
-   *
    * @return string
    *   Language code to use to compose the email.
    */
@@ -163,8 +164,6 @@ interface EmailInterface extends BaseEmailInterface {
    *
    * The account is calculated automatically from the to address.
    *
-   * Valid: after rendering.
-   *
    * @return \Drupal\Core\Session\AccountInterface
    *   The account.
    */
@@ -318,7 +317,7 @@ interface EmailInterface extends BaseEmailInterface {
   /**
    * Sets the email theme.
    *
-   * Valid: build.
+   * Valid: initialisation.
    *
    * @param string $theme_name
    *   The theme name to use for email.
diff --git a/src/InternalEmailInterface.php b/src/InternalEmailInterface.php
index fa994824d134af5320f119bb5ca48d6d223b9109..38289f99a2feefd68138ddebf4d4d87b3f4c2ffe 100644
--- a/src/InternalEmailInterface.php
+++ b/src/InternalEmailInterface.php
@@ -18,19 +18,10 @@ interface InternalEmailInterface extends EmailInterface {
    */
   public function process();
 
-  /**
-   * Ends the initialization phase.
-   *
-   * Valid: initialisation.
-   *
-   * @return $this
-   */
-  public function initDone();
-
   /**
    * Customizes the email.
    *
-   * Valid: before rendering.
+   * Valid: initialisation.
    *
    * @param string $langcode
    *   The language code.
diff --git a/src/LegacyMailerHelper.php b/src/LegacyMailerHelper.php
index 62644c50b2067d5d9a3046827cebd994c0be7bae..24d61d50e04fea794c48a44c8ef81000cffdca54 100644
--- a/src/LegacyMailerHelper.php
+++ b/src/LegacyMailerHelper.php
@@ -125,12 +125,6 @@ class LegacyMailerHelper implements LegacyMailerHelperInterface {
     $src_headers = $message['headers'];
     $dest_headers = $email->getHeaders();
 
-    // Add in 'To' header which is stored directly in the message.
-    // @see \Drupal\Core\Mail\Plugin\Mail\PhpMail::mail()
-    if (isset($message['to'])) {
-      $src_headers['to'] = $message['to'];
-    }
-
     foreach ($src_headers as $name => $value) {
       $name = strtolower($name);
       if (isset(self::SKIP_HEADERS[$name])) {
diff --git a/src/Mailer.php b/src/Mailer.php
index 45daf52e761930bf58c38c24f4e0ba48cbf2db28..4e2d045bbecf27eb975bb6f6c5f2a9492bca3e1b 100644
--- a/src/Mailer.php
+++ b/src/Mailer.php
@@ -170,15 +170,11 @@ class Mailer implements MailerInterface {
    * @internal
    */
   public function doSend(InternalEmailInterface $email) {
-    // LegacyEmailBuilder sets the theme during the process phase. Save the
-    // active theme so we can change back.
-    $active_theme_name = $this->themeManager->getActiveTheme()->getName();
-
-    // Process the build phase.
-    // @see \Drupal\symfony_mailer\EmailInterface::PHASE_BUILD
+    // Process the init phase.
+    // @see \Drupal\symfony_mailer\EmailInterface::PHASE_INIT
     $email->process();
 
-    // Do switching.
+    // Calculate required switching.
     $current_langcode = $this->languageManager->getCurrentLanguage()->getId();
     if ($email->getParam('__disable_customize__')) {
       // Undocumented setting for use from LegacyEmailBuilder only for
@@ -193,8 +189,6 @@ class Mailer implements MailerInterface {
       $account = $this->account;
     }
     else {
-      $this->changeTheme($email->getTheme());
-
       // Determine langcode and account from the to address, if there is
       // agreement.
       $langcodes = $accounts = [];
@@ -212,21 +206,22 @@ class Mailer implements MailerInterface {
 
     $email->customize($langcode, $account);
 
-    $must_switch_account = $account->id() != $this->account->id();
+    // Do switching.
+    $active_theme_name = $this->changeTheme($email->getTheme());
 
+    $must_switch_account = $account->id() != $this->account->id();
     if ($must_switch_account) {
       $this->accountSwitcher->switchTo($account);
     }
 
     $must_switch_language = $langcode !== $current_langcode;
-
     if ($must_switch_language) {
       $this->changeActiveLanguage($langcode);
     }
 
     try {
-      // Process the pre-render phase.
-      // @see \Drupal\symfony_mailer\EmailInterface::PHASE_PRE_RENDER
+      // Process the build phase.
+      // @see \Drupal\symfony_mailer\EmailInterface::PHASE_BUILD
       $email->process();
 
       // Render.
@@ -299,7 +294,7 @@ class Mailer implements MailerInterface {
   /**
    * {@inheritdoc}
    */
-  public function changeTheme(string $theme_name) {
+  protected function changeTheme(string $theme_name) {
     $active_theme_name = $this->themeManager->getActiveTheme()->getName();
     if ($theme_name !== $active_theme_name) {
       $this->themeManager->setActiveTheme($this->themeInitialization->initTheme($theme_name));
diff --git a/src/MailerInterface.php b/src/MailerInterface.php
index adba1243c35e36cdcd7ce77c4294351ddbed3891..6945502a1588cd6a0cd5b75ac12737bfcf01941b 100644
--- a/src/MailerInterface.php
+++ b/src/MailerInterface.php
@@ -18,15 +18,4 @@ interface MailerInterface {
    */
   public function send(InternalEmailInterface $email);
 
-  /**
-   * Changes the active theme.
-   *
-   * @param string $theme_name
-   *   The theme name.
-   *
-   * @return string
-   *   The previously active theme name.
-   */
-  public function changeTheme(string $theme_name);
-
 }
diff --git a/src/Plugin/EmailAdjuster/HooksEmailAdjuster.php b/src/Plugin/EmailAdjuster/HooksEmailAdjuster.php
index 9a2fd3832d876f24f1adad8b754760a1071cf154..4538eb856244d9b20996a787f23ad7924c783984 100644
--- a/src/Plugin/EmailAdjuster/HooksEmailAdjuster.php
+++ b/src/Plugin/EmailAdjuster/HooksEmailAdjuster.php
@@ -29,17 +29,28 @@ class HooksEmailAdjuster extends EmailAdjusterBase {
    */
   public function init(EmailInterface $email) {
     $this->moduleHandler = \Drupal::moduleHandler();
+    $this->invokeHooks($email);
+  }
 
-    foreach (EmailInterface::PHASE_NAMES as $phase => $name) {
-      if ($phase == EmailInterface::PHASE_INIT) {
-        // Call init hooks immediately.
-        $this->invokeHooks($email);
-      }
-      else {
-        // Add processor to invoke hooks later.
-        $email->addProcessor([$this, 'invokeHooks'], $phase, EmailInterface::DEFAULT_WEIGHT, "hook_mailer_$name");
-      }
-    }
+  /**
+   * {@inheritdoc}
+   */
+  public function build(EmailInterface $email) {
+    $this->invokeHooks($email);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function postRender(EmailInterface $email) {
+    $this->invokeHooks($email);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function postSend(EmailInterface $email) {
+    $this->invokeHooks($email);
   }
 
   /**
@@ -52,7 +63,7 @@ class HooksEmailAdjuster extends EmailAdjusterBase {
    * @see hook_mailer_TYPE_PHASE()
    * @see hook_mailer_TYPE__SUBTYPE_PHASE()
    */
-  public function invokeHooks(EmailInterface $email) {
+  protected function invokeHooks(EmailInterface $email) {
     $name = EmailInterface::PHASE_NAMES[$email->getPhase()];
     $type = $email->getType();
     $sub_type = $email->getSubType();
diff --git a/src/Plugin/EmailAdjuster/ThemeEmailAdjuster.php b/src/Plugin/EmailAdjuster/ThemeEmailAdjuster.php
index a5c587748574891257221af36f80bb526939060e..3466d3745f794aaa67bc817c4ee2c9e3e121fccd 100644
--- a/src/Plugin/EmailAdjuster/ThemeEmailAdjuster.php
+++ b/src/Plugin/EmailAdjuster/ThemeEmailAdjuster.php
@@ -18,7 +18,6 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
  *   id = "email_theme",
  *   label = @Translation("Theme"),
  *   description = @Translation("Sets the email theme."),
- *   weight = 0,
  * )
  */
 class ThemeEmailAdjuster extends EmailAdjusterBase implements ContainerFactoryPluginInterface {
@@ -86,7 +85,7 @@ class ThemeEmailAdjuster extends EmailAdjusterBase implements ContainerFactoryPl
   /**
    * {@inheritdoc}
    */
-  public function build(EmailInterface $email) {
+  public function init(EmailInterface $email) {
     $theme_name = $this->getEmailTheme();
     $email->setTheme($theme_name);
   }
diff --git a/src/Plugin/EmailAdjuster/ToEmailAdjuster.php b/src/Plugin/EmailAdjuster/ToEmailAdjuster.php
index dc30d9dc96bea86b40cab4fd3593d14b0b86ca3c..e932dcc16463ecd8246a25e014010e7d77dd1cc6 100644
--- a/src/Plugin/EmailAdjuster/ToEmailAdjuster.php
+++ b/src/Plugin/EmailAdjuster/ToEmailAdjuster.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\symfony_mailer\Plugin\EmailAdjuster;
 
+use Drupal\symfony_mailer\EmailInterface;
+
 /**
  * Defines the To Email Adjuster.
  *
@@ -18,4 +20,17 @@ class ToEmailAdjuster extends AddressAdjusterBase {
    */
   protected const NAME = 'to';
 
+  /**
+   * {@inheritdoc}
+   */
+  public function init(EmailInterface $email) {
+    parent::build($email);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build(EmailInterface $email) {
+  }
+
 }
diff --git a/src/Plugin/EmailBuilder/CommerceOrderEmailBuilder.php b/src/Plugin/EmailBuilder/CommerceOrderEmailBuilder.php
index cfe93903f99ad80c00b56319066bafd0fa150f62..d5fdda7810836aff9b99ff50a090d4553fcd7041 100644
--- a/src/Plugin/EmailBuilder/CommerceOrderEmailBuilder.php
+++ b/src/Plugin/EmailBuilder/CommerceOrderEmailBuilder.php
@@ -79,14 +79,21 @@ class CommerceOrderEmailBuilder extends EmailBuilderBase {
   /**
    * {@inheritdoc}
    */
-  public function build(EmailInterface $email) {
+  public function init(EmailInterface $email) {
     $order = $email->getParam('commerce_order_item');
-    $store = $order->getStore();
     $customer = $order->getCustomer();
     $to = $customer->isAuthenticated() ? $customer : $order->getEmail();
+    $email->setTo($to);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build(EmailInterface $email) {
+    $order = $email->getParam('commerce_order_item');
+    $store = $order->getStore();
 
-    $email->setTo($to)
-      ->setBodyEntity($order, 'email')
+    $email->setBodyEntity($order, 'email')
       ->addLibrary('symfony_mailer/commerce_order')
       ->setVariable('order_number', $order->getOrderNumber())
       ->setVariable('store', $store->getName());
diff --git a/src/Plugin/EmailBuilder/ContactEmailBuilder.php b/src/Plugin/EmailBuilder/ContactEmailBuilder.php
index 74d9138aa92523306be8103b1a56479a47ec91b0..f46d7a079e98de94db8793db659f787d138ed95e 100644
--- a/src/Plugin/EmailBuilder/ContactEmailBuilder.php
+++ b/src/Plugin/EmailBuilder/ContactEmailBuilder.php
@@ -55,6 +55,15 @@ class ContactEmailBuilder extends ContactEmailBuilderBase {
     return $factory->newTypedEmail('contact', $key, $contact_message, $sender, $message['params']['recipient']);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function init(EmailInterface $email) {
+    if ($email->getSubType() == 'mail') {
+      $email->setTo($email->getParam('recipient'));
+    }
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -64,10 +73,6 @@ class ContactEmailBuilder extends ContactEmailBuilderBase {
 
     $email->setVariable('recipient_name', $recipient->getDisplayName())
       ->setVariable('recipient_edit_url', $recipient->toUrl('edit-form')->toString());
-
-    if ($email->getSubType() == 'mail') {
-      $email->setTo($recipient);
-    }
   }
 
 }
diff --git a/src/Plugin/EmailBuilder/ContactEmailBuilderBase.php b/src/Plugin/EmailBuilder/ContactEmailBuilderBase.php
index 30c22dd626fb6c9a9e2b168d2b7e2d660a0c8c67..a47b132434b8837668e7a738b5b484655caa1401 100644
--- a/src/Plugin/EmailBuilder/ContactEmailBuilderBase.php
+++ b/src/Plugin/EmailBuilder/ContactEmailBuilderBase.php
@@ -14,6 +14,15 @@ class ContactEmailBuilderBase extends EmailBuilderBase {
 
   use MailerHelperTrait;
 
+  /**
+   * {@inheritdoc}
+   */
+  public function init(EmailInterface $email) {
+    if ($email->getSubType() != 'mail') {
+      $email->setTo($email->getParam('sender'));
+    }
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -32,9 +41,6 @@ class ContactEmailBuilderBase extends EmailBuilderBase {
     if ($email->getSubType() == 'mail') {
       $email->setReplyTo($sender);
     }
-    else {
-      $email->setTo($sender);
-    }
   }
 
 }
diff --git a/src/Plugin/EmailBuilder/LegacyEmailBuilder.php b/src/Plugin/EmailBuilder/LegacyEmailBuilder.php
index 3e76849438a8e28fc975a416ba8d6bbd9dbef0c9..12a2cefca1e3300e1247bf1028adcb3af03fdef5 100644
--- a/src/Plugin/EmailBuilder/LegacyEmailBuilder.php
+++ b/src/Plugin/EmailBuilder/LegacyEmailBuilder.php
@@ -8,6 +8,7 @@ use Drupal\symfony_mailer\EmailFactoryInterface;
 use Drupal\symfony_mailer\EmailInterface;
 use Drupal\symfony_mailer\Exception\SkipMailException;
 use Drupal\symfony_mailer\LegacyMailerHelperInterface;
+use Drupal\symfony_mailer\MailerHelperTrait;
 use Drupal\symfony_mailer\MailerInterface;
 use Drupal\symfony_mailer\Processor\EmailBuilderBase;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -17,6 +18,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
  */
 class LegacyEmailBuilder extends EmailBuilderBase implements ContainerFactoryPluginInterface {
 
+  use MailerHelperTrait;
+
   /**
    * The legacy mailer helper.
    *
@@ -98,6 +101,18 @@ class LegacyEmailBuilder extends EmailBuilderBase implements ContainerFactoryPlu
       ->setParam('__disable_customize__', TRUE);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function init(EmailInterface $email) {
+    // Add in 'To' header which is stored directly in the message.
+    // @see \Drupal\Core\Mail\Plugin\Mail\PhpMail::mail()
+    $message = $email->getParam('legacy_message');
+    if (isset($message['to'])) {
+      $email->setTo($this->helper()->parseAddress($message['to']));
+    }
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -114,11 +129,6 @@ class LegacyEmailBuilder extends EmailBuilderBase implements ContainerFactoryPlu
       $message['headers']['Reply-to'] = $reply;
     }
 
-    // Force changing theme early to ensure that hook_mail() is called with the
-    // correct theme. The mailer will restore the original theme.
-    $this->theme = $email->getTheme();
-    $this->mailer->changeTheme($this->theme);
-
     // Build the email by invoking hook_mail() on this module.
     $args = [$message['key'], &$message, $message['params']];
     $this->moduleHandler->invoke($message['module'], 'mail', $args);
@@ -136,14 +146,4 @@ class LegacyEmailBuilder extends EmailBuilderBase implements ContainerFactoryPlu
     $this->legacyHelper->emailFromArray($email, $message);
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function preRender(EmailInterface $email) {
-    // Check the theme wasn't changed after our build() function ran.
-    if ($this->theme != $email->getTheme()) {
-      throw new \Exception("Mail theme changed after rendering legacy Email");
-    }
-  }
-
 }
diff --git a/src/Plugin/EmailBuilder/SimplenewsEmailBuilderBase.php b/src/Plugin/EmailBuilder/SimplenewsEmailBuilderBase.php
index c59b7c27bacd9a6422e3d43f4a010a36d6de8b2a..15e6026e18e980ff3d471d14a6a60a9a0689c54a 100644
--- a/src/Plugin/EmailBuilder/SimplenewsEmailBuilderBase.php
+++ b/src/Plugin/EmailBuilder/SimplenewsEmailBuilderBase.php
@@ -19,7 +19,7 @@ class SimplenewsEmailBuilderBase extends EmailBuilderBase {
   /**
    * {@inheritdoc}
    */
-  public function build(EmailInterface $email) {
+  public function init(EmailInterface $email) {
     // @todo Add a method SubscriberInterface::getAddress().
     $subscriber = $email->getParam('simplenews_subscriber');
     $address = new Address($subscriber->getMail(), NULL, $subscriber->getLangcode(), $subscriber->getUser());
diff --git a/src/Plugin/EmailBuilder/TestEmailBuilder.php b/src/Plugin/EmailBuilder/TestEmailBuilder.php
index 137d04631ac679dbfef8a81d3a34325eac213ac1..f3dc2bd61fc4c23128943d8fb69878d2ae9f6948 100644
--- a/src/Plugin/EmailBuilder/TestEmailBuilder.php
+++ b/src/Plugin/EmailBuilder/TestEmailBuilder.php
@@ -38,11 +38,16 @@ class TestEmailBuilder extends EmailBuilderBase {
   /**
    * {@inheritdoc}
    */
-  public function build(EmailInterface $email) {
+  public function init(EmailInterface $email) {
     if ($to = $email->getParam('to')) {
       $email->setTo($to);
     }
+  }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function build(EmailInterface $email) {
     $logo = \Drupal::service('extension.list.module')->getPath('symfony_mailer') . '/logo.png';
     $logo_uri = \Drupal::service('file_url_generator')->generateString($logo);
 
diff --git a/src/Plugin/EmailBuilder/UpdateEmailBuilder.php b/src/Plugin/EmailBuilder/UpdateEmailBuilder.php
index a73455cf7cc9b665c7dfb54eeea950f24be5d0b1..c6efee5eec1ea9c985f4ff9bbf67e5f085fdb551 100644
--- a/src/Plugin/EmailBuilder/UpdateEmailBuilder.php
+++ b/src/Plugin/EmailBuilder/UpdateEmailBuilder.php
@@ -54,6 +54,10 @@ class UpdateEmailBuilder extends EmailBuilderBase {
    * {@inheritdoc}
    */
   public function build(EmailInterface $email) {
+    if (empty($email->getTo())) {
+      throw new SkipMailException('No update notification address configured.');
+    }
+
     $config = $this->helper()->config();
     $notify_all = ($config->get('update.settings')->get('notification.threshold') == 'all');
     \Drupal::moduleHandler()->loadInclude('update', 'install');
@@ -80,23 +84,6 @@ class UpdateEmailBuilder extends EmailBuilderBase {
     }
   }
 
-  /**
-   * Skip sending the update email when no 'To' header is configured.
-   *
-   * This check has to be done after
-   * {@see \Drupal\symfony_mailer\EmailInterface::PHASE_BUILD} because
-   * otherwise the 'To' header might not be set yet.
-   *
-   * @throws \Drupal\symfony_mailer\Exception\SkipMailException
-   *
-   * @see \Drupal\symfony_mailer\EmailInterface::PHASE_PRE_RENDER
-   */
-  public function preRender(EmailInterface $email): void {
-    if (empty($email->getTo())) {
-      throw new SkipMailException('No update notification address configured.');
-    }
-  }
-
   /**
    * {@inheritdoc}
    */
diff --git a/src/Plugin/EmailBuilder/UserEmailBuilder.php b/src/Plugin/EmailBuilder/UserEmailBuilder.php
index ab753e381ec713520ba84f689ca8f7fac9299b90..0524bff0e085aa668dc156d07ee759118283c911 100644
--- a/src/Plugin/EmailBuilder/UserEmailBuilder.php
+++ b/src/Plugin/EmailBuilder/UserEmailBuilder.php
@@ -94,10 +94,16 @@ class UserEmailBuilder extends EmailBuilderBase {
   /**
    * {@inheritdoc}
    */
-  public function build(EmailInterface $email) {
+  public function init(EmailInterface $email) {
     if ($email->getSubType() != 'register_pending_approval_admin') {
       $email->setTo($email->getParam('user'));
     }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build(EmailInterface $email) {
     $this->tokenOptions(['callback' => 'user_mail_tokens', 'clear' => TRUE]);
   }
 
diff --git a/src/Processor/EmailAdjusterManager.php b/src/Processor/EmailAdjusterManager.php
index 8a699dd61d0750f16da4835398118d5777d2002e..7442cd1861dc1ce376ecf82cb162abf83bbed9e4 100644
--- a/src/Processor/EmailAdjusterManager.php
+++ b/src/Processor/EmailAdjusterManager.php
@@ -47,7 +47,7 @@ class EmailAdjusterManager extends DefaultPluginManager implements EmailAdjuster
     // Add adjusters.
     foreach ($policy_config as $plugin_id => $config) {
       if ($this->hasDefinition($plugin_id)) {
-        $this->createInstance($plugin_id, $config)->init($email);
+        $email->addProcessor($this->createInstance($plugin_id, $config));
       }
     }
   }
diff --git a/src/Processor/EmailProcessorInterface.php b/src/Processor/EmailProcessorInterface.php
index 9e1c992bda60cde39b88772dabfbeab0014d0e1d..cd85b538b764e71bc68cd13af2ebc18139a2376d 100644
--- a/src/Processor/EmailProcessorInterface.php
+++ b/src/Processor/EmailProcessorInterface.php
@@ -10,19 +10,7 @@ use Drupal\symfony_mailer\EmailInterface;
 interface EmailProcessorInterface {
 
   /**
-   * Mapping from phase to default function name.
-   *
-   * @var string[]
-   */
-  public const FUNCTION_NAMES = [
-    EmailInterface::PHASE_BUILD => 'build',
-    EmailInterface::PHASE_PRE_RENDER => 'preRender',
-    EmailInterface::PHASE_POST_RENDER => 'postRender',
-    EmailInterface::PHASE_POST_SEND => 'postSend',
-  ];
-
-  /**
-   * Initializes an email to call this email processor.
+   * Process emails during the initialise phase.
    *
    * @param \Drupal\symfony_mailer\EmailInterface $email
    *   The email to initialize.
@@ -32,26 +20,13 @@ interface EmailProcessorInterface {
   /**
    * Process emails during the build phase.
    *
-   * Must not trigger any rendering because cannot yet rely on the correct
-   * language, theme, and account. For example, must not cast a translatable
-   * string into a plain string, or replace tokens.
+   * The language, theme, and account are now correct.
    *
    * @param \Drupal\symfony_mailer\EmailInterface $email
    *   The email to process.
    */
   public function build(EmailInterface $email);
 
-  /**
-   * Process emails during the pre-render phase.
-   *
-   * Not normally needed. Only if there is a rendering step that needs to be
-   * done before the main rendering call.
-   *
-   * @param \Drupal\symfony_mailer\EmailInterface $email
-   *   The email to process.
-   */
-  public function preRender(EmailInterface $email);
-
   /**
    * Process emails during the post-render phase.
    *
diff --git a/src/Processor/EmailProcessorTrait.php b/src/Processor/EmailProcessorTrait.php
index 93b0537b404833515939ace23f3fb260358369a9..2c43653ae3583d25ae8912cb27c679480839e8f4 100644
--- a/src/Processor/EmailProcessorTrait.php
+++ b/src/Processor/EmailProcessorTrait.php
@@ -13,9 +13,6 @@ trait EmailProcessorTrait {
    * {@inheritdoc}
    */
   public function init(EmailInterface $email) {
-    foreach (self::FUNCTION_NAMES as $phase => $function) {
-      $email->addProcessor([$this, $function], $phase, $this->getWeight($phase), $this->getId());
-    }
   }
 
   /**
@@ -24,12 +21,6 @@ trait EmailProcessorTrait {
   public function build(EmailInterface $email) {
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function preRender(EmailInterface $email) {
-  }
-
   /**
    * {@inheritdoc}
    */
diff --git a/symfony_mailer.api.php b/symfony_mailer.api.php
index 5113ed37eb05b87c34b36ec54de2bad5a100cbd6..51de2630ce8d26d87bc767ebc0d819f6a9c2065f 100644
--- a/symfony_mailer.api.php
+++ b/symfony_mailer.api.php
@@ -19,12 +19,14 @@ function hook_mailer_PHASE(EmailInterface $email) {
   // hook_mailer_init():
   // - Add a class.
   // - Re-use an EmailAdjuster.
-  (new CustomEmailProcessor())->init($email);
+  $email->addProcessor(new CustomEmailProcessor());
   $config = ['message' => 'Unpopular user skipped'];
   Drupal::service('plugin.manager.email_adjuster')->createInstance('email_skip_sending', $config)->init($email);
 
-  // hook_mailer_build():
+  // hook_mailer_init():
   $email->setTo('user@example.com');
+
+  // hook_mailer_build():
   $body = $email->getBody();
   $body['extra'] = ['#markup' => 'Extra text'];
   $email->setBody($body);
diff --git a/tests/modules/symfony_mailer_test/symfony_mailer_test.module b/tests/modules/symfony_mailer_test/symfony_mailer_test.module
index 47bff6c43478619d91c139c7d09fa8259e384cca..12269547b75d47c840117549d7708735e3c00ab3 100644
--- a/tests/modules/symfony_mailer_test/symfony_mailer_test.module
+++ b/tests/modules/symfony_mailer_test/symfony_mailer_test.module
@@ -11,5 +11,5 @@ use Drupal\symfony_mailer\EmailInterface;
  * Implements hook_mailer_init().
  */
 function symfony_mailer_test_mailer_init(EmailInterface $email) {
-  \Drupal::service('symfony_mailer.test')->init($email);
+  $email->addProcessor(\Drupal::service('symfony_mailer.test'));
 }