diff --git a/config/schema/workbench_email.schema.yml b/config/schema/workbench_email.schema.yml
index 262657e..b33ab47 100644
--- a/config/schema/workbench_email.schema.yml
+++ b/config/schema/workbench_email.schema.yml
@@ -22,27 +22,17 @@ workbench_email.workbench_email_template.*:
     subject:
       type: label
       label: 'Subject'
-    roles:
+    recipient_types:
       type: sequence
-      label: 'Roles'
+      label: 'Enabled recipient types'
       sequence:
-        type: string
-        label: 'Role'
+        type: workbench_email_recipient_type
     bundles:
       type: sequence
       label: 'Bundles'
       sequence:
         type: string
         label: 'Bundle'
-    fields:
-      type: sequence
-      label: 'Mail fields'
-      sequence:
-        type: string
-        label: 'Field'
-    author:
-      type: boolean
-      label: 'Send to author'
     body:
       type: mapping
       label: 'Body'
@@ -53,3 +43,57 @@ workbench_email.workbench_email_template.*:
         format:
           type: string
           label: 'Format'
+    dependencies:
+      type: config_dependencies
+      label: 'Dependencies'
+
+workbench_email_recipient_type:
+  type: mapping
+  label: 'Recipient type'
+  mapping:
+    id:
+      type: string
+      label: 'ID'
+    provider:
+      type: string
+      label: 'Provider'
+    status:
+      type: boolean
+      label: 'Status'
+    settings:
+      type: workbench_email_recipient_type_settings.[%parent.id]
+
+# Default for plugins without any schema.
+workbench_email_recipient_type_settings.*:
+  type: mapping
+  label: 'Recipient type settings'
+
+workbench_email_recipient_type_settings.email:
+  type: mapping
+  mapping:
+    fields:
+      type: sequence
+      label: 'Email fields'
+      sequence:
+        type: string
+        label: 'Email field'
+
+workbench_email_recipient_type_settings.role:
+  type: mapping
+  mapping:
+    roles:
+      type: sequence
+      label: 'Restrict to the selected roles'
+      sequence:
+        type: string
+        label: 'Role'
+
+workbench_email_recipient_type_settings.entity_reference_user:
+  type: mapping
+  mapping:
+    fields:
+      type: sequence
+      label: 'Entity Reference fields'
+      sequence:
+        type: string
+        label: 'Entity Reference field'
diff --git a/src/Annotation/RecipientType.php b/src/Annotation/RecipientType.php
new file mode 100644
index 0000000..4d49166
--- /dev/null
+++ b/src/Annotation/RecipientType.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace Drupal\workbench_email\Annotation;
+
+use Drupal\Component\Annotation\Plugin;
+
+/**
+ * Defines a Recipient type annotation object.
+ *
+ * Plugin Namespace: Plugin\workbench_email\RecipientType.
+ *
+ * @see \Drupal\workbench_email\RecipientTypePluginManager
+ * @see \Drupal\workbench_email\Plugin\RecipientTypeInterface
+ * @see \Drupal\workbench_email\Plugin\RecipientTypeBase
+ * @see plugin_api
+ *
+ * @Annotation
+ */
+class RecipientType extends Plugin {
+
+  /**
+   * The plugin ID.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The name of the provider that owns the recipient type.
+   *
+   * @var string
+   */
+  public $provider;
+
+  /**
+   * The human-readable name of the recipient type.
+   *
+   * This is used as an administrative summary of what the recipient type does.
+   *
+   * @var \Drupal\Core\Annotation\Translation
+   *
+   * @ingroup plugin_translatable
+   */
+  public $title;
+
+  /**
+   * Additional administrative information about the recipient type's behavior.
+   *
+   * @var \Drupal\Core\Annotation\Translation
+   *
+   * @ingroup plugin_translatable
+   */
+  public $description = '';
+
+  /**
+   * Whether this recipient type is enabled or disabled by default.
+   *
+   * @var bool
+   */
+  public $status = FALSE;
+
+  /**
+   * The default settings for the recipient type.
+   *
+   * @var array
+   */
+  public $settings = [];
+
+}
diff --git a/src/Entity/Template.php b/src/Entity/Template.php
index f904260..cf03208 100644
--- a/src/Entity/Template.php
+++ b/src/Entity/Template.php
@@ -2,7 +2,11 @@
 
 namespace Drupal\workbench_email\Entity;
 
+use Drupal\Component\Plugin\PluginInspectionInterface;
 use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\workbench_email\Plugin\RecipientTypeInterface;
+use Drupal\workbench_email\RecipientTypePluginCollection;
 use Drupal\workbench_email\TemplateInterface;
 
 /**
@@ -68,18 +72,29 @@ class Template extends ConfigEntityBase implements TemplateInterface {
   protected $subject;
 
   /**
-   * Fields to get email from.
+   * Configured recipient types for this template.
    *
-   * @var string[]
+   * An associative array of recipient types assigned to the email template,
+   * keyed by the instance ID of each recipient type and using the properties:
+   * - id: The plugin ID of the recipient type plugin instance.
+   * - provider: The name of the provider that owns the recipient type.
+   * - status: (optional) A Boolean indicating whether the recipient type is
+   *   enabled for the email template. Defaults to FALSE.
+   * - settings: (optional) An array of configured settings for the recipient
+   *   type.
+   *
+   * Use Template::recipientTypes() to access the actual recipient types.
+   *
+   * @var array
    */
-  protected $fields = [];
+  protected $recipient_types = [];
 
   /**
-   * Roles to send to.
+   * Holds the collection of recipient types that are attached to this template.
    *
-   * @var string[]
+   * @var \Drupal\workbench_email\RecipientTypePluginCollection
    */
-  protected $roles = [];
+  protected $recipientTypeCollection;
 
   /**
    * Entity bundles.
@@ -88,13 +103,6 @@ class Template extends ConfigEntityBase implements TemplateInterface {
    */
   protected $bundles = [];
 
-  /**
-   * Send to entity owner.
-   *
-   * @var bool
-   */
-  protected $author = FALSE;
-
   /**
    * {@inheritdoc}
    */
@@ -128,82 +136,83 @@ class Template extends ConfigEntityBase implements TemplateInterface {
   /**
    * {@inheritdoc}
    */
-  public function isAuthor() {
-    return $this->author;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setAuthor($author) {
-    $this->author = $author;
-    return $this;
+  public function recipientTypes($instance_id = NULL) {
+    if (!isset($this->recipientTypeCollection)) {
+      $this->recipientTypeCollection = new RecipientTypePluginCollection(\Drupal::service('plugin.manager.recipient_type'), $this->recipient_types);
+      $this->recipientTypeCollection->sort();
+    }
+    if (isset($instance_id)) {
+      return $this->recipientTypeCollection->get($instance_id);
+    }
+    return $this->recipientTypeCollection;
   }
 
   /**
    * {@inheritdoc}
    */
-  public function getFields() {
-    return $this->fields;
+  public function getPluginCollections() {
+    return ['recipient_types' => $this->recipientTypes()];
   }
 
   /**
    * {@inheritdoc}
    */
-  public function setFields(array $fields) {
-    $this->fields = $fields;
+  public function calculateDependencies() {
+    parent::calculateDependencies();
+    foreach ($this->bundles as $bundle) {
+      list($entity_type_id, $bundle_id) = explode(':', $bundle, 2);
+      $entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
+      $bundle_config_dependency = $entity_type->getBundleConfigDependency($bundle_id);
+      $this->addDependency($bundle_config_dependency['type'], $bundle_config_dependency['name']);
+    }
     return $this;
   }
 
   /**
    * {@inheritdoc}
    */
-  public function getRoles() {
-    return $this->roles;
+  protected function calculatePluginDependencies(PluginInspectionInterface $instance) {
+    // Only add dependencies for plugins that are actually configured.
+    if (isset($this->recipient_types[$instance->getPluginId()])) {
+      parent::calculatePluginDependencies($instance);
+    }
   }
 
   /**
    * {@inheritdoc}
    */
-  public function setRoles(array $roles) {
-    $this->roles = $roles;
-    return $this;
+  public function getBundles() {
+    return $this->bundles;
   }
 
   /**
    * {@inheritdoc}
    */
-  public function calculateDependencies() {
-    parent::calculateDependencies();
-    foreach ($this->roles as $role) {
-      $this->addDependency('config', 'user.role.' . $role);
-    }
-    foreach ($this->fields as $field) {
-      list($entity_type, $field_name) = explode(':', $field, 2);
-      $this->addDependency('config', "field.storage.$entity_type.$field_name");
-    }
-    foreach ($this->bundles as $bundle) {
-      list($entity_type_id, $bundle_id) = explode(':', $bundle, 2);
-      $entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
-      $bundle_config_dependency = $entity_type->getBundleConfigDependency($bundle_id);
-      $this->addDependency($bundle_config_dependency['type'], $bundle_config_dependency['name']);
-    }
+  public function setBundles(array $bundles) {
+    $this->bundles = $bundles;
     return $this;
   }
 
   /**
    * {@inheritdoc}
    */
-  public function getBundles() {
-    return $this->bundles;
+  public function getRecipients(ContentEntityInterface $entity) {
+    $recipients = [];
+    foreach ($this->recipient_types as $plugin_id => $config) {
+      $recipients = array_merge($recipients, $this->recipientTypes($plugin_id)->prepareRecipients($entity, $this));
+    }
+    return array_filter(array_unique($recipients));
   }
 
   /**
    * {@inheritdoc}
    */
-  public function setBundles($bundles) {
-    $this->bundles = $bundles;
-    return $this;
+  public function onDependencyRemoval(array $dependencies) {
+    // Give the parent method and each recipient type plugin a chance to react
+    // to removed dependencies and report if any of them made a change.
+    return array_reduce(iterator_to_array($this->recipientTypes()), function ($carry, RecipientTypeInterface $type) use ($dependencies) {
+      return $type->onDependencyRemoval($dependencies) || $carry;
+    }, parent::onDependencyRemoval($dependencies));
   }
 
 }
diff --git a/src/EventSubscriber/WorkbenchTransitionEventSubscriber.php b/src/EventSubscriber/WorkbenchTransitionEventSubscriber.php
index db7e022..c1a8cc9 100644
--- a/src/EventSubscriber/WorkbenchTransitionEventSubscriber.php
+++ b/src/EventSubscriber/WorkbenchTransitionEventSubscriber.php
@@ -7,7 +7,6 @@ use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Plugin\PluginBase;
 use Drupal\Core\Queue\QueueFactory;
 use Drupal\Core\Session\AccountInterface;
-use Drupal\user\EntityOwnerInterface;
 use Drupal\workbench_email\QueuedEmail;
 use Drupal\workbench_email\TemplateInterface;
 use Drupal\workbench_moderation\Event\WorkbenchModerationEvents;
@@ -77,7 +76,7 @@ class WorkbenchTransitionEventSubscriber implements EventSubscriberInterface {
     ])) {
       // Filter out any that the user doesn't have access to or that don't have
       // any email templates.
-      $transitions = array_filter($transitions, function(ModerationStateTransitionInterface $transition) {
+      $transitions = array_filter($transitions, function (ModerationStateTransitionInterface $transition) {
         return $this->currentUser->hasPermission(sprintf('use %s transition', $transition->id())) && $transition->getThirdPartySetting('workbench_email', 'workbench_email_templates', []);
       });
       if (!$transitions) {
@@ -122,33 +121,7 @@ class WorkbenchTransitionEventSubscriber implements EventSubscriberInterface {
    *   Array of email addresses to send to.
    */
   protected function prepareRecipients(ContentEntityInterface $entity, TemplateInterface $template) {
-    $recipients = [];
-    if ($template->isAuthor() && $entity instanceof EntityOwnerInterface) {
-      if (!$entity->getOwner()->isAnonymous()) {
-        $recipients[] = $entity->getOwner()->getEmail();
-      }
-    }
-    foreach ($template->getRoles() as $role) {
-      foreach ($this->entityTypeManager->getStorage('user')->loadByProperties([
-        'roles' => $role,
-        'status' => 1,
-      ]) as $account) {
-        $recipients[] = $account->getEmail();
-      }
-    }
-    $fields = array_filter($template->getFields(), function($field_name) use ($entity) {
-      list($entity_type, $field_name) = explode(':', $field_name, 2);
-      return $entity_type === $entity->getEntityTypeId() && $entity->hasField($field_name) && !$entity->{$field_name}->isEmpty();
-    });
-    foreach ($fields as $field) {
-      list(, $field_name) = explode(':', $field, 2);
-      /** @var \Drupal\Core\Field\FieldItemInterface $field_item */
-      foreach ($entity->{$field_name} as $field_item) {
-        $recipients[] = $field_item->get('value')->getValue();
-      }
-    }
-    return array_filter(array_unique($recipients));
+    return $template->getRecipients($entity);
   }
 
-
 }
diff --git a/src/Form/TemplateForm.php b/src/Form/TemplateForm.php
index a837d4c..93245e3 100644
--- a/src/Form/TemplateForm.php
+++ b/src/Form/TemplateForm.php
@@ -9,9 +9,7 @@ use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\field\Entity\FieldConfig;
-use Drupal\user\Entity\Role;
-use Drupal\user\RoleInterface;
+use Drupal\Core\Form\SubformState;
 use Drupal\workbench_moderation\ModerationInformationInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -57,6 +55,10 @@ class TemplateForm extends EntityForm {
    *   Entity type manager.
    * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
    *   Entity field manager.
+   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_bundle_info
+   *   The entity type bundle info.
+   * @param \Drupal\workbench_moderation\ModerationInformationInterface $moderation_info
+   *   The moderation info service.
    */
   public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, EntityTypeBundleInfoInterface $entity_bundle_info, ModerationInformationInterface $moderation_info) {
     $this->entityTypeManager = $entity_type_manager;
@@ -69,7 +71,12 @@ class TemplateForm extends EntityForm {
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container) {
-    return new static($container->get('entity_type.manager'), $container->get('entity_field.manager'), $container->get('entity_type.bundle.info'), $container->get('workbench_moderation.moderation_information'));
+    return new static(
+      $container->get('entity_type.manager'),
+      $container->get('entity_field.manager'),
+      $container->get('entity_type.bundle.info'),
+      $container->get('workbench_moderation.moderation_information')
+    );
   }
 
   /**
@@ -109,9 +116,9 @@ class TemplateForm extends EntityForm {
     ];
 
     $default_body = $workbench_email_template->getBody() + [
-        'value' => '',
-        'format' => 'plain_text',
-      ];
+      'value' => '',
+      'format' => 'plain_text',
+    ];
     $form['body'] = [
       '#type' => 'text_format',
       '#title' => $this->t('Body'),
@@ -120,63 +127,41 @@ class TemplateForm extends EntityForm {
       '#format' => $default_body['format'],
       '#default_value' => $default_body['value'],
     ];
-    // Add the roles.
-    $roles = array_filter($this->entityTypeManager->getStorage('user_role')
-      ->loadMultiple(), function (RoleInterface $role) {
-      return !in_array($role->id(), [
-        RoleInterface::ANONYMOUS_ID,
-        RoleInterface::AUTHENTICATED_ID,
-      ], TRUE);
-    });
-    $role_options = array_map(function (RoleInterface $role) {
-      return $role->label();
-    }, $roles);
-    $form['recipients'] = [
-      '#type' => 'details',
-      '#title' => $this->t('Recipients'),
-      '#open' => TRUE,
-    ];
-    $form['recipients']['roles'] = [
+
+    // Recipient types.
+    $recipient_types = $workbench_email_template->recipientTypes();
+    $form['enabled_recipient_types'] = [
       '#type' => 'checkboxes',
-      '#title' => $this->t('Roles'),
-      '#description' => $this->t('Send to all users with selected roles'),
-      '#options' => $role_options,
-      '#default_value' => $workbench_email_template->getRoles(),
+      '#title' => $this->t('Enabled recipient types'),
+      '#required' => TRUE,
+      '#options' => [],
+      '#default_value' => [],
     ];
-    // Add the fields.
-    $fields = $this->entityFieldManager->getFieldMapByFieldType('email');
-    $field_options = [];
-    foreach ($fields as $entity_type_id => $entity_type_fields) {
-      $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
-      if (!$this->moderationInfo->isModeratableEntityType($entity_type)) {
-        // These fields are irrelevant, the entity type isn't moderated.
-        continue;
+    $form['recipient_types_settings'] = [
+      '#type' => 'vertical_tabs',
+      '#title' => $this->t('Recipient type configuration'),
+    ];
+    /** @var \Drupal\workbench_email\Plugin\RecipientTypeInterface $plugin */
+    foreach ($recipient_types as $plugin_id => $plugin) {
+      $form['enabled_recipient_types']['#options'][$plugin_id] = $plugin->getLabel();
+      if ($plugin->isEnabled()) {
+        $form['enabled_recipient_types']['#default_value'][$plugin_id] = $plugin_id;
       }
-      $base = $this->entityFieldManager->getBaseFieldDefinitions($entity_type_id);
-      foreach ($entity_type_fields as $field_name => $field_detail) {
-        if (in_array($field_name, array_keys($base), TRUE)) {
-          continue;
-        }
-        $sample_bundle = reset($field_detail['bundles']);
-        $sample_field = $this->entityTypeManager->getStorage('field_config')
-          ->load($entity_type_id . '.' . $sample_bundle . '.' . $field_name);
-        $field_options[$entity_type_id . ':' . $field_name] = $sample_field->label() . ' (' . $entity_type->getLabel() . ')';
+      if ($plugin->hasFormClass('configure')) {
+        $form['recipient_types']['settings'][$plugin_id] = [
+          '#tree' => TRUE,
+          '#type' => 'details',
+          '#open' => TRUE,
+          '#title' => $plugin->getLabel(),
+          '#group' => 'recipient_types_settings',
+          '#parents' => ['recipient_types', $plugin_id, 'settings'],
+        ];
+        $subform_state = SubformState::createForSubform($form['recipient_types']['settings'][$plugin_id], $form, $form_state);
+        $form['recipient_types']['settings'][$plugin_id] += $plugin->buildConfigurationForm($form['recipient_types']['settings'][$plugin_id], $subform_state);
       }
     }
-    $form['recipients']['fields'] = [
-      '#type' => 'checkboxes',
-      '#title' => $this->t('Email Fields'),
-      '#description' => $this->t('Send to mail address found in the selected fields'),
-      '#options' => $field_options,
-      '#default_value' => $workbench_email_template->getFields(),
-    ];
-    // Add the author flag.
-    $form['recipients']['author'] = [
-      '#type' => 'checkbox',
-      '#default_value' => $workbench_email_template->isAuthor(),
-      '#title' => $this->t('Author'),
-      '#description' => $this->t('Send to entity author/owner'),
-    ];
+
+    // Bundles.
     $bundle_options = [];
     foreach ($this->entityTypeManager->getDefinitions() as $entity_type) {
       if (!$this->moderationInfo->isModeratableEntityType($entity_type) || !($bundle_entity_type = $entity_type->getBundleEntityType())) {
@@ -204,11 +189,37 @@ class TemplateForm extends EntityForm {
     return $form;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    /** @var \Drupal\workbench_email\TemplateInterface $workbench_email_template */
+    $workbench_email_template = $this->entity;
+    $recipient_types = $workbench_email_template->recipientTypes();
+    /** @var \Drupal\workbench_email\Plugin\RecipientTypeInterface $plugin */
+    foreach ($recipient_types as $plugin_id => $plugin) {
+      if ($plugin->hasFormClass('configure')) {
+        $subform_state = SubformState::createForSubform($form['recipient_types']['settings'][$plugin_id], $form, $form_state);
+        $plugin->validateConfigurationForm($form['recipient_types']['settings'][$plugin_id], $subform_state);
+      }
+    }
+    parent::validateForm($form, $form_state);
+  }
+
   /**
    * {@inheritdoc}
    */
   public function save(array $form, FormStateInterface $form_state) {
+    /** @var \Drupal\workbench_email\TemplateInterface $workbench_email_template */
     $workbench_email_template = $this->entity;
+    $recipient_types = $workbench_email_template->recipientTypes();
+    /** @var \Drupal\workbench_email\Plugin\RecipientTypeInterface $plugin */
+    foreach ($recipient_types as $plugin_id => $plugin) {
+      if ($plugin->hasFormClass('configure')) {
+        $subform_state = SubformState::createForSubform($form['recipient_types']['settings'][$plugin_id], $form, $form_state);
+        $plugin->submitConfigurationForm($form['recipient_types']['settings'][$plugin_id], $subform_state);
+      }
+    }
     $status = $workbench_email_template->save();
 
     switch ($status) {
@@ -232,8 +243,18 @@ class TemplateForm extends EntityForm {
   protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
     parent::copyFormValuesToEntity($entity, $form, $form_state);
     // Filter out unchecked items.
-    $entity->set('roles', array_filter($entity->get('roles')));
-    $entity->set('fields', array_filter($entity->get('fields')));
+    $types = [];
+    foreach (array_filter($form_state->getValue('enabled_recipient_types')) as $type) {
+      $types[$type] = [
+        'status' => TRUE,
+        'settings' => $form_state->getValue([
+          'recipient_types',
+          $type,
+          'settings',
+        ]),
+      ];
+    }
+    $entity->set('recipient_types', $types);
     $entity->set('bundles', array_filter($entity->get('bundles')));
   }
 
diff --git a/src/Plugin/QueueWorker/WorkbenchEmailProcessor.php b/src/Plugin/QueueWorker/WorkbenchEmailProcessor.php
index d9e4a0d..e081e40 100644
--- a/src/Plugin/QueueWorker/WorkbenchEmailProcessor.php
+++ b/src/Plugin/QueueWorker/WorkbenchEmailProcessor.php
@@ -84,8 +84,8 @@ class WorkbenchEmailProcessor extends QueueWorkerBase implements ContainerFactor
    *   Token service.
    * @param \Drupal\Core\Render\RendererInterface $renderer
    *   Renderer service.
-   * @param \Drupal\workbench_moderation\ModerationInformationInterface $renderer
-   *   Renderer service.
+   * @param \Drupal\workbench_moderation\ModerationInformationInterface $moderation_information
+   *   Moderation information.
    */
   public function __construct(array $configuration, $plugin_id, $plugin_definition, MailManagerInterface $mail_manager, EntityRepositoryInterface $entity_repository, Token $token, RendererInterface $renderer, ModerationInformationInterface $moderation_information) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
diff --git a/src/Plugin/RecipientType/Author.php b/src/Plugin/RecipientType/Author.php
new file mode 100644
index 0000000..b77ce24
--- /dev/null
+++ b/src/Plugin/RecipientType/Author.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Drupal\workbench_email\Plugin\RecipientType;
+
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\user\EntityOwnerInterface;
+use Drupal\workbench_email\Plugin\RecipientTypeBase;
+use Drupal\workbench_email\TemplateInterface;
+
+/**
+ * Provides a recipient type of the content author.
+ *
+ * @RecipientType(
+ *   id = "author",
+ *   title = @Translation("Author"),
+ *   description = @Translation("Send to entity author/owner."),
+ * )
+ */
+class Author extends RecipientTypeBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareRecipients(ContentEntityInterface $entity, TemplateInterface $template) {
+    $recipients = [];
+    if ($this->isEnabled() && $entity instanceof EntityOwnerInterface) {
+      if (!$entity->getOwner()->isAnonymous()) {
+        $recipients[] = $entity->getOwner()->getEmail();
+      }
+    }
+    return $recipients;
+  }
+
+}
diff --git a/src/Plugin/RecipientType/EmailField.php b/src/Plugin/RecipientType/EmailField.php
new file mode 100644
index 0000000..0793f4e
--- /dev/null
+++ b/src/Plugin/RecipientType/EmailField.php
@@ -0,0 +1,223 @@
+<?php
+
+namespace Drupal\workbench_email\Plugin\RecipientType;
+
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityFieldManagerInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Field\FieldItemInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\field\FieldStorageConfigInterface;
+use Drupal\workbench_email\Plugin\RecipientTypeBase;
+use Drupal\workbench_email\TemplateInterface;
+use Drupal\workbench_moderation\ModerationInformationInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a recipient type of an email field.
+ *
+ * @RecipientType(
+ *   id = "email",
+ *   title = @Translation("Email field"),
+ *   description = @Translation("Send to email addresses in email field."),
+ *   settings = {
+ *     "fields" = {},
+ *   },
+ * )
+ */
+class EmailField extends RecipientTypeBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * The entity field manager.
+   *
+   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
+   */
+  protected $entityFieldManager;
+
+  /**
+   * Moderation info service.
+   *
+   * @var \Drupal\workbench_moderation\ModerationInformationInterface
+   */
+  protected $moderationInfo;
+
+  /**
+   * Constructs a new EmailField object.
+   *
+   * @param array $configuration
+   *   Plugin configuration.
+   * @param string $plugin_id
+   *   The plugin ID.
+   * @param mixed $plugin_definition
+   *   The plugin definition.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
+   *   The entity field manager.
+   * @param \Drupal\workbench_moderation\ModerationInformationInterface $moderation_info
+   *   Moderation info service.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, ModerationInformationInterface $moderation_info) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->entityTypeManager = $entity_type_manager;
+    $this->entityFieldManager = $entity_field_manager;
+    $this->moderationInfo = $moderation_info;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('entity_type.manager'),
+      $container->get('entity_field.manager'),
+      $container->get('workbench_moderation.moderation_information')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+    // Add the fields.
+    $fields = $this->entityFieldManager->getFieldMapByFieldType('email');
+    $field_options = [];
+    foreach ($fields as $entity_type_id => $entity_type_fields) {
+      $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
+      if (!$this->moderationInfo->isModeratableEntityType($entity_type)) {
+        // These fields are irrelevant, the entity type isn't moderated.
+        continue;
+      }
+      $base = $this->entityFieldManager->getBaseFieldDefinitions($entity_type_id);
+      foreach ($entity_type_fields as $field_name => $field_detail) {
+        if (in_array($field_name, array_keys($base), TRUE)) {
+          continue;
+        }
+        $sample_bundle = reset($field_detail['bundles']);
+        $sample_field = $this->entityTypeManager->getStorage('field_config')
+          ->load($entity_type_id . '.' . $sample_bundle . '.' . $field_name);
+        $field_options[$entity_type_id . ':' . $field_name] = $sample_field->label() . ' (' . $entity_type->getLabel() . ')';
+      }
+    }
+    return [
+      'fields' => [
+        '#type' => 'checkboxes',
+        '#title' => $this->t('Email Fields'),
+        '#description' => $this->t('Send to mail address found in the selected fields'),
+        '#options' => $field_options,
+        '#default_value' => $this->getFields(),
+      ],
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
+    $this->setFields(array_values(array_filter($form_state->getValue('fields'))));
+    parent::submitConfigurationForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareRecipients(ContentEntityInterface $entity, TemplateInterface $template) {
+    $recipients = [];
+    $fields = array_filter($this->getFields(), function ($field_name) use ($entity) {
+      list($entity_type, $field_name) = explode(':', $field_name, 2);
+      return $entity_type === $entity->getEntityTypeId() && $entity->hasField($field_name) && !$entity->{$field_name}->isEmpty();
+    });
+    foreach ($fields as $field) {
+      list(, $field_name) = explode(':', $field, 2);
+      /** @var \Drupal\Core\Field\FieldItemInterface $field_item */
+      foreach ($entity->{$field_name} as $field_item) {
+        $recipients[] = $this->getEmailFromFieldItem($field_item);
+      }
+    }
+    return $recipients;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function calculateDependencies() {
+    $dependencies = [];
+    $field_storage = $this->entityTypeManager->getStorage('field_storage_config');
+    foreach ($this->getFields() as $identifier) {
+      list ($entity_type_id, $field_name) = explode(':', $identifier, 2);
+      if ($field = $field_storage->load("$entity_type_id.$field_name")) {
+        $dependencies[$field->getConfigDependencyKey()][] = $field->getConfigDependencyName();
+      }
+    }
+    return NestedArray::mergeDeep($dependencies, parent::calculateDependencies());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onDependencyRemoval(array $dependencies) {
+    $removed_fields = array_reduce($dependencies['config'], function (array $carry, $item) {
+      if (!$item instanceof FieldStorageConfigInterface) {
+        return $carry;
+      }
+      $carry[] = sprintf('%s:%s', $item->getTargetEntityTypeId(), $item->getName());
+      return $carry;
+    }, []);
+    if ($removed_fields && array_intersect($removed_fields, $this->getFields())) {
+      $this->setFields(array_diff($this->getFields(), $removed_fields));
+      return TRUE;
+    }
+    return FALSE;
+  }
+
+  /**
+   * Gets value of roles.
+   *
+   * @return array
+   *   Value of roles
+   */
+  protected function getFields() {
+    return $this->getConfiguration()['settings']['fields'];
+  }
+
+  /**
+   * Sets roles.
+   *
+   * @param array $fields
+   *   Field IDs in {entity_type}:{field_name} format.
+   *
+   * @return $this
+   */
+  protected function setFields(array $fields) {
+    $configuration = $this->getConfiguration();
+    $configuration['settings']['fields'] = $fields;
+    $this->setConfiguration($configuration);
+    return $this;
+  }
+
+  /**
+   * Get field value.
+   *
+   * @param \Drupal\Core\Field\FieldItemInterface $field_item
+   *   Field item.
+   *
+   * @return string
+   *   Email.
+   */
+  protected function getEmailFromFieldItem(FieldItemInterface $field_item) {
+    return $field_item->get('value')->getValue();
+  }
+
+}
diff --git a/src/Plugin/RecipientType/EntityReferenceUser.php b/src/Plugin/RecipientType/EntityReferenceUser.php
new file mode 100644
index 0000000..e533d88
--- /dev/null
+++ b/src/Plugin/RecipientType/EntityReferenceUser.php
@@ -0,0 +1,71 @@
+<?php
+
+namespace Drupal\workbench_email\Plugin\RecipientType;
+
+use Drupal\Core\Field\FieldItemInterface;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Provides a recipient type based on entity reference fields.
+ *
+ * @RecipientType(
+ *   id = "entity_reference_user",
+ *   title = @Translation("Entity Reference: User"),
+ *   description = @Translation("Send to users referenced in an entity reference field."),
+ *   settings = {
+ *     "fields" = {},
+ *   },
+ * )
+ */
+class EntityReferenceUser extends EmailField {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+    // Add the fields.
+    $fields = $this->entityFieldManager->getFieldMapByFieldType('entity_reference');
+    $field_options = [];
+    foreach ($fields as $entity_type_id => $entity_type_fields) {
+      $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
+      if (!$this->moderationInfo->isModeratableEntityType($entity_type)) {
+        // These fields are irrelevant, the entity type isn't moderated.
+        continue;
+      }
+      $base = $this->entityFieldManager->getBaseFieldDefinitions($entity_type_id);
+      foreach ($entity_type_fields as $field_name => $field_detail) {
+        if (in_array($field_name, array_keys($base), TRUE)) {
+          if ($base[$field_name]->getSetting('target_type') !== 'user') {
+            continue;
+          }
+          $field_options[$entity_type_id . ':' . $field_name] = $base[$field_name]->getLabel() . ' (' . $entity_type->getLabel() . ')';
+          continue;
+        }
+        $sample_bundle = reset($field_detail['bundles']);
+        $fields = $this->entityFieldManager->getFieldDefinitions($entity_type_id, $sample_bundle);
+        $sample_field = $fields[$field_name];
+        if ($sample_field->getSetting('target_type') !== 'user') {
+          continue;
+        }
+        $field_options[$entity_type_id . ':' . $field_name] = $sample_field->label() . ' (' . $entity_type->getLabel() . ')';
+      }
+    }
+    return [
+      'fields' => [
+        '#type' => 'checkboxes',
+        '#title' => $this->t('Entity Reference User Fields'),
+        '#description' => $this->t('Send to users referenced by the selected fields.'),
+        '#options' => $field_options,
+        '#default_value' => $this->getFields(),
+      ],
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEmailFromFieldItem(FieldItemInterface $field_item) {
+    return $field_item->entity->getEmail();
+  }
+
+}
diff --git a/src/Plugin/RecipientType/Role.php b/src/Plugin/RecipientType/Role.php
new file mode 100644
index 0000000..d626a08
--- /dev/null
+++ b/src/Plugin/RecipientType/Role.php
@@ -0,0 +1,169 @@
+<?php
+
+namespace Drupal\workbench_email\Plugin\RecipientType;
+
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\user\RoleInterface;
+use Drupal\workbench_email\Plugin\RecipientTypeBase;
+use Drupal\workbench_email\TemplateInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a recipient type of user role.
+ *
+ * @RecipientType(
+ *   id = "role",
+ *   title = @Translation("Role"),
+ *   description = @Translation("Send to all users with selected roles."),
+ *   settings = {
+ *     "roles" = {},
+ *   },
+ * )
+ */
+class Role extends RecipientTypeBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * Constructs a new Role object.
+   *
+   * @param array $configuration
+   *   Plugin configuration.
+   * @param string $plugin_id
+   *   The plugin ID.
+   * @param mixed $plugin_definition
+   *   The plugin definition.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->entityTypeManager = $entity_type_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('entity_type.manager')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+    $roles = array_filter($this->entityTypeManager->getStorage('user_role')
+      ->loadMultiple(), function (RoleInterface $role) {
+        return !in_array($role->id(), [
+          RoleInterface::ANONYMOUS_ID,
+          RoleInterface::AUTHENTICATED_ID,
+        ], TRUE);
+      });
+    $role_options = array_map(function (RoleInterface $role) {
+      return $role->label();
+    }, $roles);
+    return [
+      'roles' => [
+        '#type' => 'checkboxes',
+        '#title' => $this->t('Roles'),
+        '#description' => $this->t('Send to all users with selected roles'),
+        '#options' => $role_options,
+        '#default_value' => $this->getRoles(),
+      ],
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
+    $this->setRoles(array_filter($form_state->getValue('roles')));
+    parent::submitConfigurationForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareRecipients(ContentEntityInterface $entity, TemplateInterface $template) {
+    $recipients = [];
+    foreach ($this->getRoles() as $role) {
+      foreach ($this->entityTypeManager->getStorage('user')->loadByProperties([
+        'roles' => $role,
+        'status' => 1,
+      ]) as $account) {
+        $recipients[] = $account->getEmail();
+      }
+    }
+    return $recipients;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function calculateDependencies() {
+    $dependencies = [];
+    $role_storage = $this->entityTypeManager->getStorage('user_role');
+    foreach ($role_storage->loadMultiple($this->getRoles()) as $role) {
+      $dependencies[$role->getConfigDependencyKey()][] = $role->getConfigDependencyName();
+    }
+    return NestedArray::mergeDeep($dependencies, parent::calculateDependencies());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onDependencyRemoval(array $dependencies) {
+    $removed_roles = array_reduce($dependencies['config'], function (array $carry, $item) {
+      if (!$item instanceof RoleInterface) {
+        return $carry;
+      }
+      $carry[] = $item->id();
+      return $carry;
+    }, []);
+    if ($removed_roles && array_intersect($removed_roles, $this->getRoles())) {
+      $this->setRoles(array_diff($this->getRoles(), $removed_roles));
+      return TRUE;
+    }
+    return FALSE;
+  }
+
+  /**
+   * Gets value of roles.
+   *
+   * @return array
+   *   Value of roles
+   */
+  protected function getRoles() {
+    return $this->getConfiguration()['settings']['roles'];
+  }
+
+  /**
+   * Sets roles.
+   *
+   * @param array $roles
+   *   Role IDs.
+   *
+   * @return $this
+   */
+  protected function setRoles(array $roles) {
+    $configuration = $this->getConfiguration();
+    $configuration['settings']['roles'] = $roles;
+    $this->setConfiguration($configuration);
+    return $this;
+  }
+
+}
diff --git a/src/Plugin/RecipientTypeBase.php b/src/Plugin/RecipientTypeBase.php
new file mode 100644
index 0000000..33a5507
--- /dev/null
+++ b/src/Plugin/RecipientTypeBase.php
@@ -0,0 +1,154 @@
+<?php
+
+namespace Drupal\workbench_email\Plugin;
+
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\PluginBase;
+use Drupal\Core\Plugin\PluginWithFormsTrait;
+use Drupal\workbench_email\TemplateInterface;
+
+/**
+ * Provides a base class for Recipient type plugins.
+ *
+ * @see \Drupal\workbench_email\Annotation\RecipientType
+ * @see \Drupal\workbench_email\RecipientTypeManager
+ * @see \Drupal\workbench_email\Plugin\RecipientTypeInterface
+ * @see plugin_api
+ */
+abstract class RecipientTypeBase extends PluginBase implements RecipientTypeInterface {
+
+  use PluginWithFormsTrait;
+
+  /**
+   * The name of the provider that owns this recipient type.
+   *
+   * @var string
+   */
+  public $provider;
+
+  /**
+   * A Boolean indicating whether this recipient type is enabled.
+   *
+   * @var bool
+   */
+  public $status = FALSE;
+
+  /**
+   * An associative array containing the settings of this recipient type.
+   *
+   * @var array
+   */
+  public $settings = [];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+
+    $this->provider = $this->pluginDefinition['provider'];
+
+    $this->setConfiguration($configuration);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setConfiguration(array $configuration) {
+    if (isset($configuration['status'])) {
+      $this->status = (bool) $configuration['status'];
+    }
+    if (isset($configuration['settings'])) {
+      $this->settings = (array) $configuration['settings'];
+    }
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfiguration() {
+    return [
+      'id' => $this->getPluginId(),
+      'provider' => $this->pluginDefinition['provider'],
+      'status' => $this->status,
+      'settings' => $this->settings,
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultConfiguration() {
+    return [
+      'provider' => $this->pluginDefinition['provider'],
+      'status' => FALSE,
+      'settings' => $this->pluginDefinition['settings'],
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function calculateDependencies() {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLabel() {
+    return $this->pluginDefinition['title'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDescription() {
+    return $this->pluginDefinition['description'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareRecipients(ContentEntityInterface $entity, TemplateInterface $template) {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
+    // Nil op.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
+    // Nil op.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isEnabled() {
+    return $this->status;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onDependencyRemoval(array $dependencies) {
+    return FALSE;
+  }
+
+}
diff --git a/src/Plugin/RecipientTypeInterface.php b/src/Plugin/RecipientTypeInterface.php
new file mode 100644
index 0000000..b28a6eb
--- /dev/null
+++ b/src/Plugin/RecipientTypeInterface.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace Drupal\workbench_email\Plugin;
+
+use Drupal\Component\Plugin\PluginInspectionInterface;
+use Drupal\Component\Plugin\ConfigurablePluginInterface;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\workbench_email\TemplateInterface;
+use Drupal\Core\Plugin\PluginFormInterface;
+use Drupal\Core\Plugin\PluginWithFormsInterface;
+
+/**
+ * Defines the interface for Recipient type plugins.
+ *
+ * TODO Docs.
+ *
+ * @see \Drupal\workbench_email\Annotation\RecipientType
+ * @see \Drupal\workbench_email\RecipientTypePluginManager
+ * @see \Drupal\workbench_email\Plugin\RecipientTypeBase
+ * @see plugin_api
+ */
+interface RecipientTypeInterface extends ConfigurablePluginInterface, PluginInspectionInterface, PluginWithFormsInterface, PluginFormInterface {
+
+  /**
+   * Returns the administrative label for this recipient type plugin.
+   *
+   * @return string
+   *   The label.
+   */
+  public function getLabel();
+
+  /**
+   * Returns the administrative description for this recipient type plugin.
+   *
+   * @return string
+   *   The description.
+   */
+  public function getDescription();
+
+  /**
+   * Generates a recipient types's settings form.
+   *
+   * @param array $form
+   *   A minimally prepopulated form array.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The state of the (entire) configuration form.
+   *
+   * @return array
+   *   The $form array with additional form elements for the settings of this
+   *   recipient type. The submitted form values should match $this->settings.
+   */
+  public function buildConfigurationForm(array $form, FormStateInterface $form_state);
+
+  /**
+   * Returns email address(s) matching this recipient type's configuration.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   Entity being transitioned.
+   * @param \Drupal\workbench_email\TemplateInterface $template
+   *   Template being used.
+   */
+  public function prepareRecipients(ContentEntityInterface $entity, TemplateInterface $template);
+
+  /**
+   * Checks status.
+   *
+   * @return bool
+   *   TRUE if enabled.
+   */
+  public function isEnabled();
+
+  /**
+   * Informs the plugin that a dependency of the recipient type will be deleted.
+   *
+   * @param array $dependencies
+   *   An array of dependencies that will be deleted keyed by dependency type.
+   *
+   * @return bool
+   *   TRUE if the template settings have been changed.
+   *
+   * @see \Drupal\Core\Config\ConfigEntityInterface::onDependencyRemoval()
+   *
+   * @todo https://www.drupal.org/node/2579743 make part of a generic interface.
+   */
+  public function onDependencyRemoval(array $dependencies);
+
+}
diff --git a/src/RecipientTypePluginCollection.php b/src/RecipientTypePluginCollection.php
new file mode 100644
index 0000000..6b282e1
--- /dev/null
+++ b/src/RecipientTypePluginCollection.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Drupal\workbench_email;
+
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Plugin\DefaultLazyPluginCollection;
+
+/**
+ * A collection of recipient types.
+ */
+class RecipientTypePluginCollection extends DefaultLazyPluginCollection {
+
+  /**
+   * All possible recipient type plugin IDs.
+   *
+   * @var array
+   */
+  protected $definitions;
+
+  /**
+   * {@inheritdoc}
+   *
+   * @return \Drupal\workbench_email\Plugin\RecipientTypeInterface
+   *   The recipient type.
+   */
+  public function &get($instance_id) {
+    return parent::get($instance_id);
+  }
+
+  /**
+   * Retrieves plugin definitions and creates an instance for each.
+   */
+  public function getAll() {
+    // Retrieve all available behavior plugin definitions.
+    if (!$this->definitions) {
+      $this->definitions = $this->manager->getDefinitions();
+    }
+    // Ensure that there is an instance of all available behavior plugins.
+    foreach ($this->definitions as $plugin_id => $definition) {
+      if (!isset($this->pluginInstances[$plugin_id])) {
+        $this->initializePlugin($plugin_id);
+      }
+    }
+    return $this->pluginInstances;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function initializePlugin($instance_id) {
+    $configuration = $this->manager->getDefinition($instance_id);
+    // Merge the actual configuration into the default configuration.
+    if (isset($this->configurations[$instance_id])) {
+      $configuration = NestedArray::mergeDeep($configuration, $this->configurations[$instance_id]);
+    }
+    $this->configurations[$instance_id] = $configuration;
+    parent::initializePlugin($instance_id);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function sort() {
+    $this->getAll();
+    return parent::sort();
+  }
+
+}
diff --git a/src/RecipientTypePluginManager.php b/src/RecipientTypePluginManager.php
new file mode 100644
index 0000000..c5212a2
--- /dev/null
+++ b/src/RecipientTypePluginManager.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Drupal\workbench_email;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Plugin\DefaultPluginManager;
+
+/**
+ * Manages recipient types.
+ *
+ * @see hook_recipient_type_info_alter()
+ * @see \Drupal\workbench_email\Annotation\RecipientType
+ * @see \Drupal\workbench_email\Plugin\RecipientTypeInterface
+ * @see \Drupal\workbench_email\Plugin\RecipientTypeBase
+ * @see plugin_api
+ */
+class RecipientTypePluginManager extends DefaultPluginManager {
+
+  /**
+   * Constructs a RecipientTypePluginManager object.
+   *
+   * @param \Traversable $namespaces
+   *   An object that implements \Traversable which contains the root paths
+   *   keyed by the corresponding namespace to look for plugin implementations.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   Cache backend instance to use.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler to invoke the alter hook with.
+   */
+  public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
+    parent::__construct('Plugin/RecipientType', $namespaces, $module_handler, 'Drupal\workbench_email\Plugin\RecipientTypeInterface', 'Drupal\workbench_email\Annotation\RecipientType');
+    $this->alterInfo('recipient_type_info');
+    $this->setCacheBackend($cache_backend, 'recipient_type_plugins');
+  }
+
+}
diff --git a/src/TemplateHtmlRouteProvider.php b/src/TemplateHtmlRouteProvider.php
index 4576586..aa67a16 100644
--- a/src/TemplateHtmlRouteProvider.php
+++ b/src/TemplateHtmlRouteProvider.php
@@ -13,6 +13,7 @@ use Symfony\Component\Routing\Route;
  * @see Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider
  */
 class TemplateHtmlRouteProvider extends AdminHtmlRouteProvider {
+
   /**
    * {@inheritdoc}
    */
diff --git a/src/TemplateInterface.php b/src/TemplateInterface.php
index 0bece96..3b8ed00 100644
--- a/src/TemplateInterface.php
+++ b/src/TemplateInterface.php
@@ -3,11 +3,13 @@
 namespace Drupal\workbench_email;
 
 use Drupal\Core\Config\Entity\ConfigEntityInterface;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
 
 /**
  * Provides an interface for defining Email Template entities.
  */
-interface TemplateInterface extends ConfigEntityInterface {
+interface TemplateInterface extends ConfigEntityInterface, EntityWithPluginCollectionInterface {
 
   /**
    * Gets the template subject.
@@ -48,61 +50,16 @@ interface TemplateInterface extends ConfigEntityInterface {
   public function setSubject($subject);
 
   /**
-   * Gets value of author.
+   * Returns collection of recipient type plugin instances or a plugin instance.
    *
-   * @return boolean
-   *   Value of author
-   */
-  public function isAuthor();
-
-  /**
-   * Sets author.
-   *
-   * @param boolean $author
-   *   New value for author.
-   *
-   * @return self
-   *   Instance called.
-   */
-  public function setAuthor($author);
-
-  /**
-   * Gets value of fields.
-   *
-   * @return string[]
-   *   Value of fields
-   */
-  public function getFields();
-
-  /**
-   * Sets fields.
-   *
-   * @param string[] $fields
-   *   New value for fields.
-   *
-   * @return self
-   *   Instance called.
-   */
-  public function setFields(array $fields);
-
-  /**
-   * Gets value of roles.
-   *
-   * @return string[]
-   *   Value of roles
-   */
-  public function getRoles();
-
-  /**
-   * Sets roles.
-   *
-   * @param string[] $roles
-   *   New value for roles.
+   * @param string $instance_id
+   *   (optional) The ID of a recipient type plugin instance to return.
    *
-   * @return self
-   *   Instance called.
+   * @return \Drupal\workbench_email\RecipientTypePluginCollection|\Drupal\workbench_email\Plugin\RecipientTypeInterface
+   *   Either the recipient type collection or a specific recipient type plugin
+   *   instance.
    */
-  public function setRoles(array $roles);
+  public function recipientTypes($instance_id = NULL);
 
   /**
    * Gets value of bundles.
@@ -121,6 +78,17 @@ interface TemplateInterface extends ConfigEntityInterface {
    * @return self
    *   Called instance.
    */
-  public function setBundles($bundles);
+  public function setBundles(array $bundles);
+
+  /**
+   * Calculates recipients.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   Entity being sent.
+   *
+   * @return array
+   *   Array of email addresses.
+   */
+  public function getRecipients(ContentEntityInterface $entity);
 
 }
diff --git a/src/TemplateListBuilder.php b/src/TemplateListBuilder.php
index 70f09f6..7d587e5 100644
--- a/src/TemplateListBuilder.php
+++ b/src/TemplateListBuilder.php
@@ -9,6 +9,7 @@ use Drupal\Core\Entity\EntityInterface;
  * Provides a listing of Email Template entities.
  */
 class TemplateListBuilder extends ConfigEntityListBuilder {
+
   /**
    * {@inheritdoc}
    */
diff --git a/tests/fixtures/update/recipient-plugins.php.gz b/tests/fixtures/update/recipient-plugins.php.gz
new file mode 100644
index 0000000..694e2c3
Binary files /dev/null and b/tests/fixtures/update/recipient-plugins.php.gz differ
diff --git a/tests/src/Functional/RecipientPluginUpdatePathTest.php b/tests/src/Functional/RecipientPluginUpdatePathTest.php
new file mode 100644
index 0000000..007b640
--- /dev/null
+++ b/tests/src/Functional/RecipientPluginUpdatePathTest.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Drupal\Tests\workbench_email\Functional;
+
+use Drupal\FunctionalTests\Update\UpdatePathTestBase;
+
+/**
+ * Defines a class for testing upgrade path.
+ *
+ * @group workbench_email
+ */
+class RecipientPluginUpdatePathTest extends UpdatePathTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setDatabaseDumpFiles() {
+    $this->databaseDumpFiles = [
+      __DIR__ . '/../../fixtures/update/recipient-plugins.php.gz',
+    ];
+  }
+
+  /**
+   * Tests workbench_email_update_8001().
+   */
+  public function testUpdatePath() {
+    $this->runUpdates();
+    $entity_type_manager = $this->container->get('entity_type.manager');
+    /** @var \Drupal\workbench_email\TemplateInterface $template */
+    $template = $entity_type_manager->getStorage('workbench_email_template')->load('send');
+    $nodes = $entity_type_manager->getStorage('node')->loadByProperties([
+      'title' => 'test node',
+    ]);
+    $recipients = $template->getRecipients(reset($nodes));
+    $expected = [
+      // User with approver role.
+      'bob@example.com',
+      // Author.
+      'admin@example.com',
+      // Email field on node.
+      'terri@example.com',
+    ];
+    $this->assertEquals(sort($expected), sort($recipients));
+    $this->assertNull($template->get('author'));
+    $this->assertNull($template->get('fields'));
+    $this->assertNull($template->get('roles'));
+  }
+
+}
diff --git a/tests/src/Functional/WorkbenchTransitionEmailTest.php b/tests/src/Functional/WorkbenchTransitionEmailTest.php
index 48cc739..1e5c7ee 100644
--- a/tests/src/Functional/WorkbenchTransitionEmailTest.php
+++ b/tests/src/Functional/WorkbenchTransitionEmailTest.php
@@ -2,34 +2,27 @@
 
 namespace Drupal\Tests\workbench_email\Functional;
 
-use Drupal\Core\Entity\Entity\EntityFormDisplay;
 use Drupal\Core\Test\AssertMailTrait;
 use Drupal\Core\Url;
-use Drupal\field\Entity\FieldConfig;
-use Drupal\field\Entity\FieldStorageConfig;
 use Drupal\node\Entity\NodeType;
-use Drupal\node\NodeTypeInterface;
-use Drupal\simpletest\BlockCreationTrait;
-use Drupal\simpletest\NodeCreationTrait;
+use Drupal\Tests\block\Traits\BlockCreationTrait;
 use Drupal\Tests\BrowserTestBase;
+use Drupal\Tests\node\Traits\NodeCreationTrait;
+use Drupal\Tests\workbench_email\Traits\WorkbenchEmailTestTrait;
 use Drupal\workbench_email\Entity\Template;
-use Drupal\workbench_moderation\Entity\ModerationState;
 use Drupal\workbench_moderation\Entity\ModerationStateTransition;
 
 /**
  * Tests the view access control handler for moderation state entities.
  *
- * @group workbench_moderation
- *
- * @runTestsInSeparateProcesses
- *
- * @preserveGlobalState disabled
+ * @group workbench_email
  */
 class WorkbenchTransitionEmailTest extends BrowserTestBase {
 
   use AssertMailTrait;
   use BlockCreationTrait;
   use NodeCreationTrait;
+  use WorkbenchEmailTestTrait;
 
   /**
    * Test node type.
@@ -123,12 +116,12 @@ class WorkbenchTransitionEmailTest extends BrowserTestBase {
       'type' => 'test',
       'name' => 'Test',
     ]);
-    $this->setupModerationForNodeType($this->nodeType);
+    $this->setUpModerationForNodeType($this->nodeType);
     $this->nodeType = NodeType::create([
       'type' => 'another',
       'name' => 'Another Test',
     ]);
-    $this->setupModerationForNodeType($this->nodeType);
+    $this->setUpModerationForNodeType($this->nodeType);
     // Create an approver role and two users.
     $this->approverRole = $this->drupalCreateRole([
       'view any unpublished content',
@@ -179,28 +172,8 @@ class WorkbenchTransitionEmailTest extends BrowserTestBase {
       'administer workbench_email templates',
       'access administration pages',
     ]);
-    // Add an email field notify to the node-type.
-    FieldStorageConfig::create([
-      'cardinality' => 1,
-      'entity_type' => 'node',
-      'field_name' => 'field_email',
-      'type' => 'email',
-    ])->save();
-    FieldConfig::create([
-      'field_name' => 'field_email',
-      'bundle' => 'test',
-      'label' => 'Notify',
-      'entity_type' => 'node',
-    ])->save();
-    if (!$entity_form_display = EntityFormDisplay::load('node.test.default')) {
-      $entity_form_display = EntityFormDisplay::create([
-        'targetEntityType' => 'node',
-        'bundle' => 'test',
-        'mode' => 'default',
-        'status' => TRUE,
-      ]);
-    }
-    $entity_form_display->setComponent('field_email', ['type' => 'email_default'])->save();
+    // Add an email field notify to the test bundle.
+    $this->setUpEmailFieldForNodeBundle();
   }
 
   /**
@@ -236,8 +209,11 @@ class WorkbenchTransitionEmailTest extends BrowserTestBase {
       'label' => 'Content approved',
       'body[value]' => 'Content with title [node:title] was approved. You can view it at [node:url].',
       'subject' => 'Content approved: [node:title]',
-      'fields[node:field_email]' => TRUE,
-      'author' => TRUE,
+      'enabled_recipient_types[author]' => TRUE,
+      'enabled_recipient_types[email]' => TRUE,
+      'enabled_recipient_types[role]' => TRUE,
+      'recipient_types[email][settings][fields][node:field_email]' => TRUE,
+      'recipient_types[role][settings][roles][editor]' => TRUE,
     ], t('Save'));
     $assert->pageTextContains('Created the Content approved Email Template');
     $page->clickLink('Add Email Template');
@@ -247,7 +223,8 @@ class WorkbenchTransitionEmailTest extends BrowserTestBase {
       'label' => 'Content needs review',
       'body[value]' => 'Content with title [node:title] needs review. You can view it at [node:url].',
       'subject' => 'Content needs review',
-      'roles[approver]' => TRUE,
+      'enabled_recipient_types[role]' => TRUE,
+      'recipient_types[role][settings][roles][approver]' => TRUE,
       'bundles[node:test]' => TRUE,
     ], t('Save'));
     $assert->pageTextContains('Created the Content needs review Email Template');
@@ -314,8 +291,8 @@ class WorkbenchTransitionEmailTest extends BrowserTestBase {
     $this->assertTrue($last && isset($last['to']) && $last['to'] == $this->approver2->mail->value);
     $this->assertEquals(sprintf('Content needs review: %s', $node->getTitle()), $last['subject']);
     $this->assertEquals(sprintf('Content needs review: %s', $node->getTitle()), $prev['subject']);
-    $this->assertContains(sprintf('Content with title %s needs review. You can view it at %s', $node->label(), $node->toUrl('canonical', ['absolute' => TRUE])->toString()), preg_replace('/\s+/', ' ', $prev['body']));
-    $this->assertContains(sprintf('Content with title %s needs review. You can view it at %s', $node->label(), $node->toUrl('canonical', ['absolute' => TRUE])->toString()), preg_replace('/\s+/', ' ', $last['body']));
+    $this->assertContains(sprintf('Content with title %s needs review. You can view it at', $node->label()), preg_replace('/\s+/', ' ', $prev['body']));
+    $this->assertContains(sprintf('Content with title %s needs review. You can view it at', $node->label()), preg_replace('/\s+/', ' ', $last['body']));
 
     // Now try again going straight to needs review (no draft).
     // Reset collected email.
@@ -336,8 +313,8 @@ class WorkbenchTransitionEmailTest extends BrowserTestBase {
     $this->assertTrue($last && isset($last['to']) && $last['to'] == $this->approver2->mail->value);
     $this->assertEquals(sprintf('Content needs review: %s', $node2->getTitle()), $last['subject']);
     $this->assertEquals(sprintf('Content needs review: %s', $node2->getTitle()), $prev['subject']);
-    $this->assertContains(sprintf('Content with title %s needs review. You can view it at %s', $node2->label(), $node2->toUrl('canonical', ['absolute' => TRUE])->toString()), preg_replace('/\s+/', ' ', $prev['body']));
-    $this->assertContains(sprintf('Content with title %s needs review. You can view it at %s', $node2->label(), $node2->toUrl('canonical', ['absolute' => TRUE])->toString()), preg_replace('/\s+/', ' ', $last['body']));
+    $this->assertContains(sprintf('Content with title %s needs review. You can view it at', $node2->label()), preg_replace('/\s+/', ' ', $prev['body']));
+    $this->assertContains(sprintf('Content with title %s needs review. You can view it at', $node2->label()), preg_replace('/\s+/', ' ', $last['body']));
 
     // Login as approver and transition to approved.
     $this->drupalLogin($this->approver1);
@@ -351,8 +328,8 @@ class WorkbenchTransitionEmailTest extends BrowserTestBase {
     $this->assertTrue($last && isset($last['to']) && $last['to'] == 'foo@example.com');
     $this->assertEquals(sprintf('Content approved: %s', $node->getTitle()), $last['subject']);
     $this->assertEquals(sprintf('Content approved: %s', $node->getTitle()), $prev['subject']);
-    $this->assertContains(sprintf('Content with title %s was approved. You can view it at %s', $node->label(), $node->toUrl('canonical', ['absolute' => TRUE])->toString()), preg_replace('/\s+/', ' ', $prev['body']));
-    $this->assertContains(sprintf('Content with title %s was approved. You can view it at %s', $node->label(), $node->toUrl('canonical', ['absolute' => TRUE])->toString()), preg_replace('/\s+/', ' ', $last['body']));
+    $this->assertContains(sprintf('Content with title %s was approved. You can view it at', $node->label()), preg_replace('/\s+/', ' ', $prev['body']));
+    $this->assertContains(sprintf('Content with title %s was approved. You can view it at', $node->label()), preg_replace('/\s+/', ' ', $last['body']));
     // Try with the other node type, that isn't enabled.
     // Log back in as editor.
     $this->drupalLogin($this->editor);
@@ -371,19 +348,4 @@ class WorkbenchTransitionEmailTest extends BrowserTestBase {
     $this->assertEmpty($captured_emails);
   }
 
-  /**
-   * Enables moderation for a given node type.
-   *
-   * @param \Drupal\node\NodeTypeInterface $node_type
-   *   Node type to enable moderation for.
-   */
-  protected function setupModerationForNodeType(NodeTypeInterface $node_type) {
-    $node_type->setThirdPartySetting('workbench_moderation', 'enabled', TRUE);
-    $states = array_keys(ModerationState::loadMultiple());
-    $node_type->setThirdPartySetting('workbench_moderation', 'allowed_moderation_states', $states);
-    $node_type->setThirdPartySetting('workbench_moderation', 'default_moderation_state', 'draft');
-    $node_type->save();
-  }
-
 }
-
diff --git a/tests/src/Kernel/ConfigDependenciesTest.php b/tests/src/Kernel/ConfigDependenciesTest.php
new file mode 100644
index 0000000..5caed56
--- /dev/null
+++ b/tests/src/Kernel/ConfigDependenciesTest.php
@@ -0,0 +1,158 @@
+<?php
+
+namespace Drupal\Tests\workbench_email\Kernel;
+
+use Drupal\field\Entity\FieldConfig;
+use Drupal\KernelTests\KernelTestBase;
+use Drupal\user\Entity\Role;
+use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
+use Drupal\Tests\workbench_email\Traits\WorkbenchEmailTestTrait;
+use Drupal\workbench_email\Entity\Template;
+
+/**
+ * Defines a class for testing config dependencies.
+ *
+ * @group workbench_email
+ */
+class ConfigDependenciesTest extends KernelTestBase {
+
+  use ContentTypeCreationTrait;
+  use WorkbenchEmailTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'node',
+    'text',
+    'system',
+    'user',
+    'workbench_email',
+    'workbench_moderation',
+    'field',
+  ];
+
+  /**
+   * The template being tested.
+   *
+   * @var \Drupal\workbench_email\TemplateInterface
+   */
+  protected $template;
+
+  /**
+   * The editor role.
+   *
+   * @var \Drupal\user\Entity\Role
+   */
+  protected $editorRole;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->installEntitySchema('node');
+    $this->installConfig([
+      'node',
+      'workbench_moderation',
+      'workbench_email',
+      'system',
+    ]);
+    $this->installEntitySchema('user');
+    $this->installSchema('system', ['key_value', 'sequences']);
+    $node_type = $this->createContentType(['type' => 'test']);
+    $this->setUpModerationForNodeType($node_type);
+    $this->setUpEmailFieldForNodeBundle();
+    $this->editorRole = Role::create(['id' => 'editor']);
+    $this->editorRole->save();
+    $this->template = $this->setUpTemplate();
+  }
+
+  /**
+   * Tests scheme dependencies.
+   */
+  public function testSchemeDependencies() {
+    $this->assertEquals([
+      'config' => [
+        'field.storage.node.field_email',
+        'user.role.editor',
+      ],
+    ], $this->template->getDependencies());
+
+    // Delete the editor role.
+    $this->editorRole->delete();
+    $this->template = $this->loadUnchangedTemplate($this->template->id());
+    $this->assertEquals([
+      'config' => [
+        'field.storage.node.field_email',
+      ],
+    ], $this->template->getDependencies());
+
+    // Delete the email field.
+    FieldConfig::load('node.test.field_email')->delete();
+    $this->template = $this->loadUnchangedTemplate($this->template->id());
+    $this->assertEquals([], $this->template->getDependencies());
+  }
+
+  /**
+   * Creates a test email template.
+   *
+   * @param string $id
+   *   The id for the template.
+   *
+   * @return \Drupal\workbench_email\Entity\Template
+   *   Created template.
+   */
+  protected function setUpTemplate($id = 'test_template') {
+    $template = Template::create([
+      'id' => $id,
+      'label' => ucfirst(str_replace('_', ' ', $id)),
+      'recipient_types' => [
+        'role' => [
+          'id' => 'role',
+          'provider' => 'workbench_email',
+          'status' => 1,
+          'settings' => [
+            'roles' => [
+              'editor' => 'editor',
+            ],
+          ],
+        ],
+        'author' => [
+          'id' => 'author',
+          'provider' => 'workbench_email',
+          'status' => 1,
+          'settings' => [],
+        ],
+        'email' => [
+          'id' => 'email',
+          'provider' => 'workbench_email',
+          'status' => 1,
+          'settings' => [
+            'fields' => [
+              'node:field_email',
+            ],
+          ],
+        ],
+      ],
+    ]);
+    $template->save();
+    return $template;
+  }
+
+  /**
+   * Loads the given template.
+   *
+   * @param string $template_id
+   *   Template ID.
+   *
+   * @return \Drupal\workbench_email\TemplateInterface
+   *   Unchanged scheme.
+   */
+  protected function loadUnchangedTemplate($template_id) {
+    return $this->container->get('entity_type.manager')
+      ->getStorage('workbench_email_template')
+      ->loadUnchanged($template_id);
+  }
+
+}
diff --git a/tests/src/Kernel/RecipientTypePluginsTest.php b/tests/src/Kernel/RecipientTypePluginsTest.php
new file mode 100644
index 0000000..4872e08
--- /dev/null
+++ b/tests/src/Kernel/RecipientTypePluginsTest.php
@@ -0,0 +1,204 @@
+<?php
+
+namespace Drupal\Tests\workbench_email\Kernel;
+
+use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait;
+use Drupal\KernelTests\KernelTestBase;
+use Drupal\node\Entity\Node;
+use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
+use Drupal\Tests\user\Traits\UserCreationTrait;
+use Drupal\Tests\workbench_email\Traits\WorkbenchEmailTestTrait;
+use Drupal\user\Entity\Role;
+use Drupal\workbench_email\Entity\Template;
+use Drupal\workbench_moderation\Entity\ModerationStateTransition;
+
+/**
+ * Defines a class for testing handlers.
+ *
+ * @group workbench_email
+ */
+class RecipientTypePluginsTest extends KernelTestBase {
+
+  use WorkbenchEmailTestTrait;
+  use ContentTypeCreationTrait;
+  use UserCreationTrait;
+  use EntityReferenceTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'node',
+    'text',
+    'filter',
+    'system',
+    'user',
+    'workbench_email',
+    'workbench_moderation',
+    'field',
+  ];
+
+  /**
+   * Author.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $author;
+
+  /**
+   * Approver.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $approver;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    parent::setUp();
+    $this->installEntitySchema('node');
+    $this->installConfig([
+      'node',
+      'workbench_moderation',
+      'workbench_email',
+      'system',
+      'filter',
+    ]);
+    $this->installEntitySchema('user');
+    $this->installSchema('system', ['key_value', 'sequences']);
+    $this->installSchema('node', ['node_access']);
+    $node_type = $this->createContentType(['type' => 'test']);
+
+    $this->setUpModerationForNodeType($node_type);
+    $this->setUpEmailFieldForNodeBundle();
+    $this->createEntityReferenceField('node', 'test', 'field_approver', 'Approver', 'user');
+    Role::create(['id' => 'editor'])->save();
+    $editor = $this->createUser(['use draft_needs_review transition']);
+    $editor->setEmail('editor@example.com');
+    $editor->addRole('editor');
+    $editor->save();
+    \Drupal::service('account_switcher')->switchTo($editor);
+
+    $this->author = $this->createUser();
+    $this->author->setEmail('author@example.com');
+    $this->author->save();
+
+    $this->approver = $this->createUser();
+    $this->approver->setEmail('approver@example.com');
+    $this->approver->save();
+  }
+
+  /**
+   * Data provider for testDelivery.
+   *
+   * @return array
+   *   Test cases.
+   */
+  public function providerDelivery() {
+    return [
+      'author' => [
+        'author',
+        ['author@example.com'],
+      ],
+      'role' => [
+        'role',
+        ['editor@example.com'],
+        [
+          'roles' => ['editor'],
+        ],
+        ['user.role.editor'],
+      ],
+      'email field' => [
+        'email',
+        ['random@example.com'],
+        [
+          'fields' => ['node:field_email'],
+        ],
+        ['field.storage.node.field_email'],
+      ],
+      'entity reference field' => [
+        'entity_reference_user',
+        ['approver@example.com'],
+        [
+          'fields' => ['node:field_approver'],
+        ],
+        ['field.storage.node.field_approver'],
+      ],
+    ];
+  }
+
+  /**
+   * Tests recipient delivery.
+   *
+   * @param string $pluginId
+   *   Recipient plugin ID.
+   * @param array $expectedRecipients
+   *   Expected recipients.
+   * @param array $settings
+   *   Plugin settings.
+   * @param array $expectedDependencies
+   *   Expected configuration dependencies.
+   *
+   * @throws \Drupal\Core\Entity\EntityStorageException
+   *
+   * @dataProvider providerDelivery
+   */
+  public function testDelivery($pluginId, array $expectedRecipients, array $settings = [], array $expectedDependencies = []) {
+    $template = Template::create([
+      'id' => 'test',
+      'label' => 'Test',
+      'body' => [
+        'value' => 'Content with title [node:title] needs review.',
+        'format' => 'plain_text',
+      ],
+      'subject' => 'Content needs review: [node:title]',
+      'recipient_types' => [
+        $pluginId => [
+          'id' => $pluginId,
+          'provider' => 'workbench_email',
+          'status' => 1,
+          'settings' => $settings,
+        ],
+      ],
+    ]);
+    $template->save();
+    if ($expectedDependencies) {
+      $dependencies = $template->calculateDependencies()->getDependencies()['config'];
+      $this->assertEquals($expectedDependencies, $dependencies);
+    }
+    $transition = ModerationStateTransition::load('draft_needs_review');
+    $transition->setThirdPartySetting('workbench_email', 'workbench_email_templates', ['test' => 'test']);
+    $transition->save();
+    $this->assertContains('workbench_email.workbench_email_template.test', $transition->calculateDependencies()->getDependencies()['config']);
+
+    $node = Node::create([
+      'title' => 'test',
+      'uid' => $this->author->id(),
+      'type' => 'test',
+      'field_email' => 'random@example.com',
+      'field_approver' => $this->approver->id(),
+      'moderation_state' => 'draft',
+    ]);
+    $node->save();
+
+    // Reset email.
+    $this->container->get('state')->set('system.test_mail_collector', []);
+
+    // Send for review.
+    $node->moderation_state = 'needs_review';
+    $node->save();
+
+    // Check mail goes to recipients.
+    $captured_emails = $this->container->get('state')->get('system.test_mail_collector') ?: [];
+    $this->assertEquals($expectedRecipients, array_map(function (array $mail) {
+      return $mail['to'];
+    }, $captured_emails));
+    foreach ($captured_emails as $email) {
+      $this->assertEquals(sprintf('Content needs review: %s', $node->getTitle()), $email['subject']);
+      $this->assertContains(sprintf('Content with title %s needs review.', $node->label()), preg_replace('/\s+/', ' ', $email['body']));
+    }
+  }
+
+}
diff --git a/tests/src/Traits/WorkbenchEmailTestTrait.php b/tests/src/Traits/WorkbenchEmailTestTrait.php
new file mode 100644
index 0000000..e084931
--- /dev/null
+++ b/tests/src/Traits/WorkbenchEmailTestTrait.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace Drupal\Tests\workbench_email\Traits;
+
+use Drupal\Core\Entity\Entity\EntityFormDisplay;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\node\NodeTypeInterface;
+use Drupal\workbench_moderation\Entity\ModerationState;
+
+/**
+ * Contains helper classes for tests to set up various configuration.
+ */
+trait WorkbenchEmailTestTrait {
+
+  /**
+   * Enables moderation for a given node type.
+   *
+   * @param \Drupal\node\NodeTypeInterface $node_type
+   *   Node type to enable moderation for.
+   */
+  protected function setUpModerationForNodeType(NodeTypeInterface $node_type) {
+    $node_type->setThirdPartySetting('workbench_moderation', 'enabled', TRUE);
+    $states = array_keys(ModerationState::loadMultiple());
+    $node_type->setThirdPartySetting('workbench_moderation', 'allowed_moderation_states', $states);
+    $node_type->setThirdPartySetting('workbench_moderation', 'default_moderation_state', 'draft');
+    $node_type->save();
+  }
+
+  /**
+   * Adds an email field to a node bundle.
+   *
+   * @param string $bundle
+   *   (optional) Bundle name. Defaults to 'test'.
+   */
+  protected function setUpEmailFieldForNodeBundle($bundle = 'test') {
+    // Add an email field notify to the bundle.
+    FieldStorageConfig::create([
+      'cardinality' => 1,
+      'entity_type' => 'node',
+      'field_name' => 'field_email',
+      'type' => 'email',
+    ])->save();
+    FieldConfig::create([
+      'field_name' => 'field_email',
+      'bundle' => 'test',
+      'label' => 'Notify',
+      'entity_type' => 'node',
+    ])->save();
+    if (!$entity_form_display = EntityFormDisplay::load(sprintf('node.%s.default', $bundle))) {
+      $entity_form_display = EntityFormDisplay::create([
+        'targetEntityType' => 'node',
+        'bundle' => $bundle,
+        'mode' => 'default',
+        'status' => TRUE,
+      ]);
+    }
+    $entity_form_display->setComponent('field_email', ['type' => 'email_default'])->save();
+  }
+
+}
diff --git a/workbench_email.links.action.yml b/workbench_email.links.action.yml
index de74537..001c403 100644
--- a/workbench_email.links.action.yml
+++ b/workbench_email.links.action.yml
@@ -3,4 +3,3 @@ entity.workbench_email_template.add_form:
   title: 'Add Email Template'
   appears_on:
     - entity.workbench_email_template.collection
-
diff --git a/workbench_email.links.menu.yml b/workbench_email.links.menu.yml
index c5733f2..524cde8 100644
--- a/workbench_email.links.menu.yml
+++ b/workbench_email.links.menu.yml
@@ -4,4 +4,3 @@ entity.workbench_email_template.collection:
   description: 'Manage Email Templates'
   parent: workbench_moderation.overview
   weight: 99
-
diff --git a/workbench_email.module b/workbench_email.module
index 594f195..0702758 100644
--- a/workbench_email.module
+++ b/workbench_email.module
@@ -4,6 +4,7 @@
  * @file
  * Provides main module functions.
  */
+
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Mail\MailFormatHelper;
 use Drupal\Core\Entity\EntityInterface;
@@ -19,7 +20,7 @@ function workbench_email_help($route_name, RouteMatchInterface $route_match) {
   switch ($route_name) {
     case 'entity.workbench_email_template.collection':
     case 'help.page.workbench_email':
-      return '<p>' . t("The Workbench Moderation Email module keeps track of when a piece of entity transitions from one state to another. Admins can create new templates to manage the contents and recipients of email sent when those transitions happen.") . '</p><p>Each template can be attached to a transition by editing the transition and selecting the templates to use.</p>';
+      return '<p>' . t("The Workbench Moderation Email module keeps track of when a piece of entity transitions from one state to another. Admins can create new templates to manage the contents and recipients of email sent when those transitions happen.") . '</p><p>' . t('Each template can be attached to a transition by editing the transition and selecting the templates to use.') . '</p>';
   }
 }
 
@@ -28,7 +29,7 @@ function workbench_email_help($route_name, RouteMatchInterface $route_match) {
  */
 function workbench_email_form_moderation_state_transition_edit_form_alter(&$form, FormStateInterface $form_state) {
   // Alter the transition form to add the fields to choose the templates.
-  /** @var ModerationStateTransitionInterface $transition */
+  /** @var \Drupal\workbench_moderation\ModerationStateTransitionInterface $transition */
 
   $form_object = $form_state->getFormObject();
   if (!in_array($form_object->getOperation(), ['edit', 'add'], TRUE)) {
@@ -72,8 +73,10 @@ function workbench_email_moderation_state_transition_presave(ModerationStateTran
     foreach ($transition->getThirdPartySetting('workbench_email', 'workbench_email_templates', []) as $template) {
       $dependencies['enforced']['config'][] = 'workbench_email.workbench_email_template.' . $template;
     }
-    // Ensure no duplicates.
-    $dependencies['enforced']['config'] = array_unique($dependencies['enforced']['config']);
+    if (!empty($dependencies['enforced']['config'])) {
+      // Ensure no duplicates.
+      $dependencies['enforced']['config'] = array_unique($dependencies['enforced']['config']);
+    }
     $transition->set('dependencies', $dependencies);
   }
 }
diff --git a/workbench_email.post_update.php b/workbench_email.post_update.php
new file mode 100644
index 0000000..05ca4b1
--- /dev/null
+++ b/workbench_email.post_update.php
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * @file
+ * Contains post update hooks.
+ */
+
+use Drupal\Core\Config\Entity\ConfigEntityUpdater;
+use Drupal\workbench_email\TemplateInterface;
+
+/**
+ * Updates config entities to use the new recipient plugins.
+ */
+function workbench_email_post_update_move_to_recipient_plugins(&$sandbox = NULL) {
+  \Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'workbench_email_template', function (TemplateInterface $template) {
+    $plugins = [];
+    if ($template->get('author')) {
+      $plugins['author'] = [
+        'id' => 'author',
+        'provider' => 'workbench_email',
+        'status' => TRUE,
+        'settings' => [],
+      ];
+    }
+    if ($roles = $template->get('roles')) {
+      $plugins['role'] = [
+        'id' => 'role',
+        'provider' => 'workbench_email',
+        'status' => TRUE,
+        'settings' => [
+          'roles' => $roles,
+        ],
+      ];
+    }
+    if ($fields = $template->get('fields')) {
+      $plugins['email'] = [
+        'id' => 'email',
+        'provider' => 'workbench_email',
+        'status' => TRUE,
+        'settings' => [
+          'fields' => $fields,
+        ],
+      ];
+    }
+    $template->set('recipient_types', $plugins);
+    $template->set('fields', NULL);
+    $template->set('roles', NULL);
+    $template->set('author', NULL);
+    return TRUE;
+  });
+}
diff --git a/workbench_email.services.yml b/workbench_email.services.yml
index 98a4cfe..d26d931 100644
--- a/workbench_email.services.yml
+++ b/workbench_email.services.yml
@@ -1,4 +1,7 @@
 services:
+  plugin.manager.recipient_type:
+    class: Drupal\workbench_email\RecipientTypePluginManager
+    parent: default_plugin_manager
   workbench_email.subscriber.workbench_transition:
     class: Drupal\workbench_email\EventSubscriber\WorkbenchTransitionEventSubscriber
     arguments: ['@entity_type.manager', '@current_user', '@queue']
