diff --git a/config/schema/inmail.schema.yml b/config/schema/inmail.schema.yml
index 5758826..fff1678 100644
--- a/config/schema/inmail.schema.yml
+++ b/config/schema/inmail.schema.yml
@@ -45,6 +45,23 @@ inmail.inmail_handler.*:
       label: 'Configuration'
       type: inmail.handler.[%parent.plugin]
 
+inmail.deliverer.*:
+  label: 'Mail deliverer'
+  type: config_entity
+  mapping:
+    id:
+      label: 'Machine-name'
+      type: string
+    label:
+      label: 'Label'
+      type: label
+    plugin:
+      label: 'Plugin'
+      type: string
+    configuration:
+      label: 'Configuration'
+      type: inmail.plugin.deliverer.[%parent.plugin]
+
 # Empty default configuration schema
 inmail.analyzer.*:
   label: 'Analyzer configuration'
@@ -54,7 +71,7 @@ inmail.handler.*:
   label: 'Handler configuration'
   type: mapping
 
-# Specific analyzer/handler configuration
+# Specific plugin configuration
 inmail.handler.moderator_forward:
   label: 'Moderator Forward handler configuration'
   type: mapping
@@ -62,3 +79,26 @@ inmail.handler.moderator_forward:
     moderator:
       label: 'Moderator address'
       type: email
+
+inmail.plugin.deliverer.imap:
+  label: 'IMAP Deliverer'
+  type: mapping
+  mapping:
+    host:
+      label: 'Host'
+      type: string
+    ssl:
+      label: 'SSL'
+      type: boolean
+    port:
+      label: 'Port'
+      type: integer
+    username:
+      label: 'Username'
+      type: string
+    password:
+      label: 'Password'
+      type: string
+    batch_size:
+      label: 'Batch size'
+      type: integer
diff --git a/inmail.links.action.yml b/inmail.links.action.yml
new file mode 100644
index 0000000..255b82b
--- /dev/null
+++ b/inmail.links.action.yml
@@ -0,0 +1,5 @@
+inmail.deliverer_add:
+  route_name: inmail.deliverer_add
+  title: 'Configure new deliverer'
+  appears_on:
+    - 'inmail.deliverer_list'
diff --git a/inmail.links.task.yml b/inmail.links.task.yml
index 0a8f7a5..7f7b939 100644
--- a/inmail.links.task.yml
+++ b/inmail.links.task.yml
@@ -4,14 +4,20 @@ inmail.settings:
   base_route: inmail.settings
   weight: 0
 
+inmail.deliverer_list:
+  route_name: inmail.deliverer_list
+  title: 'Mail deliverers'
+  base_route: inmail.settings
+  weight: 1
+
 inmail.analyzer_list:
   route_name: inmail.analyzer_list
   title: 'Message analyzers'
   base_route: inmail.settings
-  weight: 1
+  weight: 2
 
 inmail.handler_list:
   route_name: inmail.handler_list
   title: 'Message handlers'
   base_route: inmail.settings
-  weight: 2
+  weight: 3
diff --git a/inmail.module b/inmail.module
index e859b6f..b2d76a8 100644
--- a/inmail.module
+++ b/inmail.module
@@ -17,6 +17,7 @@ use Drupal\inmail\Entity\AnalyzerConfig;
  * You can read more under the following chapters:
  *   - @link processing The general message processing flow @endlink
  *   - @link mime Message parsing @endlink
+ *   - @link deliverer Fetching email over IMAP @endlink
  *   - @link analyzer Analysis of new messages @endlink
  *   - @link handler Handling analyzed messages @endlink
  *   - @link mailmute Integration with the Mailmute module @enlink
@@ -92,6 +93,16 @@ use Drupal\inmail\Entity\AnalyzerConfig;
  */
 
 /**
+ * @defgroup deliverer Fetching email over IMAP
+ * @{
+ * To process messages from an IMAP account, visit the "Mail deliverers" list
+ * (admin/config/inmail/deliverers) and configure a new deliverer, entering
+ * server details and credentials in the form. Messages are delivered and
+ * processed during Cron runs.
+ * @}
+ */
+
+/**
  * @defgroup analyzer Analyzers
  * @{
  * Analyzers evaluate messages to deduce specific information that can be used
@@ -255,3 +266,23 @@ function inmail_mail($key, &$message, $params) {
       break;
   }
 }
+
+/**
+ * Implements hook_cron().
+ */
+function inmail_cron() {
+  /** @var \Drupal\inmail\Entity\DelivererConfig[] $deliverers */
+  $deliverers = \Drupal::entityManager()->getStorage('inmail_deliverer')->loadMultiple();
+  /** @var \Drupal\Component\Plugin\PluginManagerInterface $deliverer_manager */
+  $deliverer_manager = \Drupal::service('plugin.manager.inmail.deliverer');
+  /** @var \Drupal\inmail\MessageProcessorInterface $processor */
+  $processor = \Drupal::service('inmail.processor');
+
+  // Fetch and process new mail.
+  foreach ($deliverers as $deliverer) {
+    /** @var \Drupal\inmail\Plugin\inmail\Deliverer\DelivererInterface $plugin */
+    $plugin = $deliverer_manager->createInstance($deliverer->id(), $deliverer->getConfiguration());
+    $raws = $plugin->deliver();
+    $processor->processMultiple($raws);
+  }
+}
diff --git a/inmail.routing.yml b/inmail.routing.yml
index 0a9a46b..aa9de00 100644
--- a/inmail.routing.yml
+++ b/inmail.routing.yml
@@ -65,3 +65,41 @@ entity.inmail_handler.disable:
     _controller: \Drupal\inmail\Controller\HandlerController::disable
   requirements:
     _permission: 'administer inmail'
+
+inmail.deliverer_list:
+  path: admin/config/system/inmail/deliverers
+  defaults:
+    _entity_list: 'inmail_deliverer'
+    _title: 'Mail deliverers'
+  requirements:
+    _permission: 'administer inmail'
+
+inmail.deliverer_add:
+  path: admin/config/system/inmail/deliverers/add
+  defaults:
+    _entity_form: 'inmail_deliverer.add'
+    _title: 'Configure new deliverer'
+  requirements:
+    _permission: 'administer inmail'
+
+entity.inmail_deliverer.edit_form:
+  path: admin/config/system/inmail/deliverers/{inmail_deliverer}
+  defaults:
+    _entity_form: 'inmail_deliverer.default'
+    _title_callback: \Drupal\inmail\Controller\DelivererController::titleEdit
+  requirements:
+    _permission: 'administer inmail'
+
+entity.inmail_deliverer.enable:
+  path: admin/config/system/inmail/deliverers/{inmail_deliverer}/enable
+  defaults:
+    _controller: \Drupal\inmail\Controller\DelivererController::enable
+  requirements:
+    _permission: 'administer inmail'
+
+entity.inmail_deliverer.disable:
+  path: admin/config/system/inmail/deliverers/{inmail_deliverer}/disable
+  defaults:
+    _controller: \Drupal\inmail\Controller\DelivererController::disable
+  requirements:
+    _permission: 'administer inmail'
diff --git a/inmail.services.yml b/inmail.services.yml
index 4be451c..863b9ac 100644
--- a/inmail.services.yml
+++ b/inmail.services.yml
@@ -2,6 +2,14 @@ services:
   inmail.processor:
     class: Drupal\inmail\MessageProcessor
     arguments: ['@entity.manager', '@plugin.manager.inmail.analyzer', '@plugin.manager.inmail.handler', '@logger.channel.inmail']
+  plugin.manager.inmail.deliverer:
+    class: Drupal\Core\Plugin\DefaultPluginManager
+    arguments:
+      - 'Plugin/inmail/Deliverer'
+      - '@container.namespaces'
+      - '@module_handler'
+      - 'Drupal\inmail\Plugin\inmail\Deliverer\DelivererInterface'
+      - 'Drupal\inmail\Annotation\Deliverer'
   plugin.manager.inmail.analyzer:
     class: Drupal\inmail\AnalyzerManager
     parent: default_plugin_manager
diff --git a/inmail_collect/src/Plugin/inmail/Handler/CollectHandler.php b/inmail_collect/src/Plugin/inmail/Handler/CollectHandler.php
index 235ed05..ddab6d5 100644
--- a/inmail_collect/src/Plugin/inmail/Handler/CollectHandler.php
+++ b/inmail_collect/src/Plugin/inmail/Handler/CollectHandler.php
@@ -71,7 +71,7 @@ class CollectHandler extends HandlerBase {
       'header-from' => $message->getHeader()->getFieldBody('From'),
       'header-message-id' => $message->getHeader()->getFieldBody('Message-Id'),
       'raw' => $message->toString(),
-      // @todo Add fetcher reference here. Relevant if multiple present.
+      // @todo Add deliverer reference here. Relevant if multiple present.
     );
 
     Container::create(array(
diff --git a/src/Annotation/Deliverer.php b/src/Annotation/Deliverer.php
new file mode 100644
index 0000000..8beda56
--- /dev/null
+++ b/src/Annotation/Deliverer.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\inmail\Annotation\Deliverer.
+ */
+
+namespace Drupal\inmail\Annotation;
+
+use Drupal\Component\Annotation\Plugin;
+
+/**
+ * Defines the plugin annotation of a mail deliverer.
+ *
+ * @ingroup deliverer
+ *
+ * @Annotation
+ */
+class Deliverer extends Plugin {
+
+  /**
+   * The short machine-name to uniquely identify the deliverer.
+   *
+   * @var string
+   */
+  protected $id;
+
+  /**
+   * The display label of the deliverer.
+   *
+   * @var \Drupal\Core\StringTranslation\TranslationWrapper
+   */
+  protected $label;
+
+}
diff --git a/src/Controller/DelivererController.php b/src/Controller/DelivererController.php
new file mode 100644
index 0000000..fe71948
--- /dev/null
+++ b/src/Controller/DelivererController.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\inmail\Controller\DelivererController.
+ */
+
+namespace Drupal\inmail\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\inmail\Entity\DelivererConfig;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+
+/**
+ * Route controller for mail deliverers.
+ *
+ * @ingroup deliverer
+ */
+class DelivererController extends ControllerBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static($container->get('plugin.manager.inmail.deliverer'));
+  }
+
+  /**
+   * Returns a title for the deliverer configuration edit page.
+   */
+  public function titleEdit(DelivererConfig $inmail_deliverer) {
+    return $this->t('Configure deliverer %label', array('%label' => $inmail_deliverer->label()));
+  }
+
+  /**
+   * Enables a mail deliverer.
+   */
+  public function enable(DelivererConfig $inmail_deliverer) {
+    $inmail_deliverer->enable()->save();
+    return new RedirectResponse(\Drupal::url('inmail.deliverer_list', [], ['absolute' => TRUE]));
+  }
+
+  /**
+   * Disables a mail deliverer.
+   */
+  public function disable(DelivererConfig $inmail_deliverer) {
+    $inmail_deliverer->disable()->save();
+    return new RedirectResponse(\Drupal::url('inmail.deliverer_list', [], ['absolute' => TRUE]));
+  }
+
+}
diff --git a/src/DelivererListBuilder.php b/src/DelivererListBuilder.php
new file mode 100644
index 0000000..c01b317
--- /dev/null
+++ b/src/DelivererListBuilder.php
@@ -0,0 +1,84 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\inmail\DelivererListBuilder.
+ */
+
+namespace Drupal\inmail;
+
+use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * List builder for message deliverer configurations.
+ *
+ * @ingroup deliverer
+ */
+class DelivererListBuilder extends ConfigEntityListBuilder {
+
+  /**
+   * The mail deliverer plugin manager.
+   *
+   * @var \Drupal\Component\Plugin\PluginManagerInterface
+   */
+  protected $delivererManager;
+
+  /**
+   * Constructs a new DelivererListBuilder.
+   */
+  public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, PluginManagerInterface $deliverer_manager) {
+    parent::__construct($entity_type, $storage);
+    $this->delivererManager = $deliverer_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
+    return new static(
+      $entity_type,
+      $container->get('entity.manager')->getStorage($entity_type->id()),
+      $container->get('plugin.manager.inmail.deliverer')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildHeader() {
+    $row['label'] = $this->t('Deliverer');
+    $row['plugin'] = $this->t('Plugin');
+    return $row + parent::buildHeader();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildRow(EntityInterface $entity) {
+    /** @var \Drupal\inmail\Entity\DelivererConfig $entity */
+    $plugin_id = $entity->getPluginId();
+    if ($this->delivererManager->hasDefinition($plugin_id)) {
+      $plugin_label = $this->delivererManager->getDefinition($plugin_id)['label'];
+    }
+    else {
+      $plugin_label = $this->t('Plugin missing');
+    }
+
+    $row['label'] = $this->getLabel($entity);
+    $row['plugin'] = $plugin_label;
+    return $row + parent::buildRow($entity);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
+    $operations['edit']['title'] = $this->t('Configure');
+    return $operations;
+  }
+}
diff --git a/src/Entity/AnalyzerConfig.php b/src/Entity/AnalyzerConfig.php
index 9bafd06..8569541 100644
--- a/src/Entity/AnalyzerConfig.php
+++ b/src/Entity/AnalyzerConfig.php
@@ -6,8 +6,6 @@
 
 namespace Drupal\inmail\Entity;
 
-use Drupal\Core\Config\Entity\ConfigEntityBase;
-
 /**
  * Message analyzer configuration entity.
  *
@@ -40,35 +38,7 @@ use Drupal\Core\Config\Entity\ConfigEntityBase;
  *   }
  * )
  */
-class AnalyzerConfig extends ConfigEntityBase {
-
-  /**
-   * The machine name of the analyzer configuration.
-   *
-   * @var string
-   */
-  protected $id;
-
-  /**
-   * The translatable, human-readable name of the analyzer configuration.
-   *
-   * @var string
-   */
-  protected $label;
-
-  /**
-   * The ID of the analyzer plugin for this configuration.
-   *
-   * @var string
-   */
-  protected $plugin;
-
-  /**
-   * The configuration for the plugin.
-   *
-   * @var array
-   */
-  protected $configuration = array();
+class AnalyzerConfig extends PluginConfigEntity {
 
   /**
    * The weight of the analyzer configuration.
@@ -79,39 +49,4 @@ class AnalyzerConfig extends ConfigEntityBase {
    */
   protected $weight;
 
-  /**
-   * Returns the analyzer plugin ID.
-   *
-   * @return string
-   *   The machine name of the plugin for this analyzer.
-   */
-  public function getPluginId() {
-    return $this->plugin;
-  }
-
-  /**
-   * Returns the plugin configuration stored for this analyzer.
-   *
-   * @return array
-   *   The plugin configuration. Its properties are defined by the associated
-   *   plugin.
-   */
-  public function getConfiguration() {
-    return $this->configuration;
-  }
-
-  /**
-   * Replaces the configuration stored for this analyzer.
-   *
-   * @param array $configuration
-   *   New plugin configuraion. Should match the properties defined by the
-   *   plugin referenced by ::$plugin.
-   *
-   * @return $this
-   */
-  public function setConfiguration(array $configuration) {
-    $this->configuration = $configuration;
-    return $this;
-  }
-
 }
diff --git a/src/Entity/DelivererConfig.php b/src/Entity/DelivererConfig.php
new file mode 100644
index 0000000..cee6152
--- /dev/null
+++ b/src/Entity/DelivererConfig.php
@@ -0,0 +1,39 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\inmail\Entity\DelivererConfig.
+ */
+
+namespace Drupal\inmail\Entity;
+
+/**
+ * Mail deliverer configuration entity.
+ *
+ * @ingroup deliverer
+ *
+ * @ConfigEntityType(
+ *   id = "inmail_deliverer",
+ *   label = @Translation("Mail deliverer"),
+ *   admin_permission = "administer inmail",
+ *   config_prefix = "deliverer",
+ *   handlers = {
+ *     "form" = {
+ *       "default" = "Drupal\inmail\Form\DelivererConfigurationForm",
+ *       "add" = "Drupal\inmail\Form\DelivererConfigurationForm"
+ *     },
+ *     "list_builder" = "Drupal\inmail\DelivererListBuilder"
+ *   },
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "label" = "label",
+ *     "status" = "status"
+ *   },
+ *   links = {
+ *     "edit-form" = "entity.inmail_deliverer.edit_form",
+ *     "enable" = "entity.inmail_deliverer.enable",
+ *     "disable" = "entity.inmail_deliverer.disable"
+ *   }
+ * )
+ */
+class DelivererConfig extends PluginConfigEntity {
+}
diff --git a/src/Entity/HandlerConfig.php b/src/Entity/HandlerConfig.php
index 6ecabc7..e864d5b 100644
--- a/src/Entity/HandlerConfig.php
+++ b/src/Entity/HandlerConfig.php
@@ -6,8 +6,6 @@
 
 namespace Drupal\inmail\Entity;
 
-use Drupal\Core\Config\Entity\ConfigEntityBase;
-
 /**
  * Message handler configuration entity.
  *
@@ -39,70 +37,6 @@ use Drupal\Core\Config\Entity\ConfigEntityBase;
  *   }
  * )
  */
-class HandlerConfig extends ConfigEntityBase {
-
-  /**
-   * The machine name of the handler configuration.
-   *
-   * @var string
-   */
-  protected $id;
-
-  /**
-   * The translatable, human-readable name of the handler configuration.
-   *
-   * @var string
-   */
-  protected $label;
-
-  /**
-   * The ID of the handler plugin for this configuration.
-   *
-   * @var string
-   */
-  protected $plugin;
-
-  /**
-   * The configuration for the plugin.
-   *
-   * @var array
-   */
-  protected $configuration = array();
-
-  /**
-   * Returns the handler plugin ID.
-   *
-   * @return string
-   *   The machine name of the plugin for this handler.
-   */
-  public function getPluginId() {
-    return $this->plugin;
-  }
-
-  /**
-   * Returns the plugin configuration stored for this handler.
-   *
-   * @return array
-   *   The plugin configuration. Its properties are defined by the associated
-   *   plugin.
-   */
-  public function getConfiguration() {
-    return $this->configuration;
-  }
-
-  /**
-   * Replaces the configuration stored for this handler.
-   *
-   * @param array $configuration
-   *   New plugin configuraion. Should match the properties defined by the
-   *   plugin referenced by ::$plugin.
-   *
-   * @return $this
-   */
-  public function setConfiguration(array $configuration) {
-    $this->configuration = $configuration;
-    return $this;
-  }
-
+class HandlerConfig extends PluginConfigEntity {
   // @todo Implement HandlerConfig::calculateDependencies() https://www.drupal.org/node/2379929
 }
diff --git a/src/Entity/PluginConfigEntity.php b/src/Entity/PluginConfigEntity.php
new file mode 100644
index 0000000..d3ac97b
--- /dev/null
+++ b/src/Entity/PluginConfigEntity.php
@@ -0,0 +1,78 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\inmail\Entity\PluginConfigEntity.
+ */
+
+namespace Drupal\inmail\Entity;
+
+use Drupal\Core\Config\Entity\ConfigEntityBase;
+
+/**
+ * Defines a config entity skeleton for plugin configuration.
+ */
+abstract class PluginConfigEntity extends ConfigEntityBase {
+
+  /**
+   * The machine name of the plugin configuration.
+   *
+   * @var string
+   */
+  protected $id;
+
+  /**
+   * The translatable, human-readable name of the plugin configuration.
+   *
+   * @var string
+   */
+  protected $label;
+
+  /**
+   * The ID of the plugin for this configuration.
+   *
+   * @var string
+   */
+  protected $plugin;
+
+  /**
+   * The configuration for the plugin.
+   *
+   * @var array
+   */
+  protected $configuration = array();
+
+  /**
+   * Returns the plugin ID.
+   *
+   * @return string
+   *   The machine name of this plugin.
+   */
+  public function getPluginId() {
+    return $this->plugin;
+  }
+
+  /**
+   * Returns the configuration stored for this plugin.
+   *
+   * @return array
+   *   The plugin configuration. Its properties are defined by the associated
+   *   plugin.
+   */
+  public function getConfiguration() {
+    return $this->configuration;
+  }
+
+  /**
+   * Replaces the configuration stored for this plugin.
+   *
+   * @param array $configuration
+   *   New plugin configuraion. Should match the properties defined by the
+   *   plugin referenced by ::$plugin.
+   *
+   * @return $this
+   */
+  public function setConfiguration(array $configuration) {
+    $this->configuration = $configuration;
+    return $this;
+  }
+}
diff --git a/src/Form/AnalyzerConfigurationForm.php b/src/Form/AnalyzerConfigurationForm.php
index 87104f3..956f386 100644
--- a/src/Form/AnalyzerConfigurationForm.php
+++ b/src/Form/AnalyzerConfigurationForm.php
@@ -6,11 +6,7 @@
 
 namespace Drupal\inmail\Form;
 
-use Drupal\Core\Config\Entity\ConfigEntityStorageInterface;
-use Drupal\Core\Entity\EntityForm;
-use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\inmail\AnalyzerManagerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -22,29 +18,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
  *
  * @ingroup analyzer
  */
-class AnalyzerConfigurationForm extends EntityForm {
-
-  /**
-   * The message analyzer plugin manager.
-   *
-   * @var \Drupal\inmail\AnalyzerManagerInterface
-   */
-  protected $analyzerManager;
-
-  /**
-   * The entity storage for analyzer configurations.
-   *
-   * @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface
-   */
-  protected $storage;
-
-  /**
-   * {@inheritdoc}
-   */
-  public function __construct(AnalyzerManagerInterface $analyzer_manager, ConfigEntityStorageInterface $storage) {
-    $this->analyzerManager = $analyzer_manager;
-    $this->storage = $storage;
-  }
+class AnalyzerConfigurationForm extends PluginConfigurationForm {
 
   /**
    * {@inheritdoc}
@@ -59,83 +33,9 @@ class AnalyzerConfigurationForm extends EntityForm {
   /**
    * {@inheritdoc}
    */
-  public function buildForm(array $form, FormStateInterface $form_state) {
-    $form = parent::buildForm($form, $form_state);
-    /** @var \Drupal\inmail\Entity\AnalyzerConfig $entity */
-    $entity = $this->getEntity();
-
-    // Load plugin instance.
-    /** @var \Drupal\inmail\Plugin\inmail\Analyzer\AnalyzerInterface $plugin */
-    $plugin = $this->analyzerManager->createInstance($entity->getPluginId(), $entity->getConfiguration());
-    $form_state->set('plugin', $plugin);
-
-    $form['label'] = array(
-      '#title' => $this->t('Label'),
-      '#type' => 'textfield',
-      '#default_value' => $entity->label(),
-    );
-
-    $form['id'] = array(
-      '#type' => 'machine_name',
-      '#default_value' => $entity->id(),
-      '#disabled' => !$entity->isNew(),
-      '#machine_name' => array(
-        'exists' => array($this, 'exists'),
-      ),
-    );
-
-    $form['status'] = array(
-      '#title' => $this->t('Enabled'),
-      '#type' => 'checkbox',
-      '#default_value' => TRUE,
-    );
-
-    $form['configuration'] = $plugin->buildConfigurationForm(array(), $form_state);
-
-    return $form;
-  }
-
-  /**
-   * Determines if the analyzer already exists.
-   *
-   * @param string $id
-   *   The analyzer configuration ID.
-   *
-   * @return bool
-   *   TRUE if the analyzer exists, FALSE otherwise.
-   */
-  public function exists($id) {
-    return (!is_null($this->storage->load($id)));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function validateForm(array &$form, FormStateInterface $form_state) {
-    parent::validateForm($form, $form_state);
-    /** @var \Drupal\inmail\Plugin\inmail\Analyzer\AnalyzerInterface $plugin */
-    $plugin = $form_state->get('plugin');
-    $plugin->validateConfigurationForm($form, $form_state);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function submitForm(array &$form, FormStateInterface $form_state) {
     parent::submitForm($form, $form_state);
 
-    // Let the plugin update its configuration from the form.
-    /** @var \Drupal\inmail\Plugin\inmail\Analyzer\AnalyzerInterface $plugin */
-    $plugin = $form_state->get('plugin');
-    $plugin->submitConfigurationForm($form, $form_state);
-
-    // Copy plugin configuration to the entity for persistence. The reason for
-    // not doing this by overriding copyFormValuesToEntity is that the plugin
-    // submit handler has to happen first.
-    /** @var \Drupal\inmail\Entity\AnalyzerConfig $entity */
-    $entity = $this->getEntity();
-    $entity->setConfiguration($plugin->getConfiguration());
-
     $form_state->setRedirect('inmail.analyzer_list');
   }
 
diff --git a/src/Form/DelivererConfigurationForm.php b/src/Form/DelivererConfigurationForm.php
new file mode 100644
index 0000000..e04f37e
--- /dev/null
+++ b/src/Form/DelivererConfigurationForm.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\inmail\Form\DelivererConfigurationForm.
+ */
+
+namespace Drupal\inmail\Form;
+
+use Drupal\Core\Form\FormStateInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Configuration form for deliverers.
+ *
+ * Deliverer plugins that inherit
+ * \Drupal\Component\Plugin\ConfigurablePluginInterface may specify
+ * plugin-specific configuration.
+ *
+ * @ingroup deliverer
+ */
+class DelivererConfigurationForm extends PluginConfigurationForm {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('plugin.manager.inmail.deliverer'),
+      $container->get('entity.manager')->getStorage('inmail_deliverer')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    parent::submitForm($form, $form_state);
+
+    $form_state->setRedirect('inmail.deliverer_list');
+  }
+
+}
diff --git a/src/Form/HandlerConfigurationForm.php b/src/Form/HandlerConfigurationForm.php
index 629108c..4818f32 100644
--- a/src/Form/HandlerConfigurationForm.php
+++ b/src/Form/HandlerConfigurationForm.php
@@ -6,11 +6,7 @@
 
 namespace Drupal\inmail\Form;
 
-use Drupal\Core\Config\Entity\ConfigEntityStorageInterface;
-use Drupal\Core\Entity\EntityForm;
-use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\inmail\HandlerManagerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -22,29 +18,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
  *
  * @ingroup handler
  */
-class HandlerConfigurationForm extends EntityForm {
-
-  /**
-   * The message handler plugin manager.
-   *
-   * @var \Drupal\inmail\HandlerManagerInterface
-   */
-  protected $handlerManager;
-
-  /**
-   * The entity storage for handler configurations.
-   *
-   * @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface
-   */
-  protected $storage;
-
-  /**
-   * {@inheritdoc}
-   */
-  public function __construct(HandlerManagerInterface $handler_manager, ConfigEntityStorageInterface $storage) {
-    $this->handlerManager = $handler_manager;
-    $this->storage = $storage;
-  }
+class HandlerConfigurationForm extends PluginConfigurationForm {
 
   /**
    * {@inheritdoc}
@@ -59,83 +33,9 @@ class HandlerConfigurationForm extends EntityForm {
   /**
    * {@inheritdoc}
    */
-  public function buildForm(array $form, FormStateInterface $form_state) {
-    $form = parent::buildForm($form, $form_state);
-    /** @var \Drupal\inmail\Entity\HandlerConfig $entity */
-    $entity = $this->getEntity();
-
-    // Load plugin instance.
-    /** @var \Drupal\inmail\Plugin\inmail\Handler\HandlerInterface $plugin */
-    $plugin = $this->handlerManager->createInstance($entity->getPluginId(), $entity->getConfiguration());
-    $form_state->set('plugin', $plugin);
-
-    $form['label'] = array(
-      '#title' => $this->t('Label'),
-      '#type' => 'textfield',
-      '#default_value' => $entity->label(),
-    );
-
-    $form['id'] = array(
-      '#type' => 'machine_name',
-      '#default_value' => $entity->id(),
-      '#disabled' => !$entity->isNew(),
-      '#machine_name' => array(
-        'exists' => array($this, 'exists'),
-      ),
-    );
-
-    $form['status'] = array(
-      '#title' => $this->t('Enabled'),
-      '#type' => 'checkbox',
-      '#default_value' => TRUE,
-    );
-
-    $form['configuration'] = $plugin->buildConfigurationForm(array(), $form_state);
-
-    return $form;
-  }
-
-  /**
-   * Determines if the handler already exists.
-   *
-   * @param string $id
-   *   The handler configuration ID.
-   *
-   * @return bool
-   *   TRUE if the handler exists, FALSE otherwise.
-   */
-  public function exists($id) {
-    return (!is_null($this->storage->load($id)));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function validateForm(array &$form, FormStateInterface $form_state) {
-    parent::validateForm($form, $form_state);
-    /** @var \Drupal\inmail\Plugin\inmail\Handler\HandlerInterface $plugin */
-    $plugin = $form_state->get('plugin');
-    $plugin->validateConfigurationForm($form, $form_state);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function submitForm(array &$form, FormStateInterface $form_state) {
     parent::submitForm($form, $form_state);
 
-    // Let the plugin update its configuration from the form.
-    /** @var \Drupal\inmail\Plugin\inmail\Handler\HandlerInterface $plugin */
-    $plugin = $form_state->get('plugin');
-    $plugin->submitConfigurationForm($form, $form_state);
-
-    // Copy plugin configuration to the entity for persistence. The reason for
-    // not doing this by overriding copyFormValuesToEntity is that the plugin
-    // submit handler has to happen first.
-    /** @var \Drupal\inmail\Entity\HandlerConfig $entity */
-    $entity = $this->getEntity();
-    $entity->setConfiguration($plugin->getConfiguration());
-
     $form_state->setRedirect('inmail.handler_list');
   }
 
diff --git a/src/Form/InmailSettingsForm.php b/src/Form/InmailSettingsForm.php
index 73ff94a..d04b213 100644
--- a/src/Form/InmailSettingsForm.php
+++ b/src/Form/InmailSettingsForm.php
@@ -47,11 +47,11 @@ class InmailSettingsForm extends ConfigFormBase {
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
+    parent::submitForm($form, $form_state);
+
     $this->config('inmail.settings')
       ->set('return_path', $form_state->getValue('return_path'))
       ->save();
-
-    parent::submitForm($form, $form_state);
   }
 
   /**
diff --git a/src/Form/PluginConfigurationForm.php b/src/Form/PluginConfigurationForm.php
new file mode 100644
index 0000000..2da3b02
--- /dev/null
+++ b/src/Form/PluginConfigurationForm.php
@@ -0,0 +1,188 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\inmail\Form\PluginConfigurationForm.
+ */
+
+namespace Drupal\inmail\Form;
+
+use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Core\Config\Entity\ConfigEntityStorageInterface;
+use Drupal\Core\Entity\EntityForm;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Configuration form for configurable plugins.
+ */
+class PluginConfigurationForm extends EntityForm {
+
+  /**
+   * The injected plugin manager.
+   *
+   * @var \Drupal\Component\Plugin\PluginManagerInterface
+   */
+  protected $pluginManager;
+
+  /**
+   * The entity storage for plugin configurations.
+   *
+   * @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface
+   */
+  protected $storage;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(PluginManagerInterface $plugin_manager, ConfigEntityStorageInterface $storage) {
+    $this->pluginManager = $plugin_manager;
+    $this->storage = $storage;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $form = parent::buildForm($form, $form_state);
+    /** @var \Drupal\inmail\Entity\PluginConfigEntity $entity */
+    $entity = $this->getEntity();
+
+    $form['label'] = array(
+      '#title' => $this->t('Label'),
+      '#type' => 'textfield',
+      '#default_value' => $entity->label(),
+      '#required' => TRUE,
+    );
+
+    $form['id'] = array(
+      '#type' => 'machine_name',
+      '#default_value' => $entity->id(),
+      '#disabled' => !$entity->isNew(),
+      '#machine_name' => array(
+        'exists' => array($this, 'exists'),
+      ),
+    );
+
+    $form['status'] = array(
+      '#title' => $this->t('Enabled'),
+      '#type' => 'checkbox',
+      '#default_value' => TRUE,
+    );
+
+    $form['plugin_container'] = array(
+      '#type' => 'container',
+      '#prefix' => '<div id="inmail-plugin">',
+      '#suffix' => '</div>',
+    );
+
+    // Unless editing an existing plugin config, show plugin select field.
+    if ($entity->isNew()) {
+      $form['plugin_container']['plugin'] = array(
+        '#type' => 'select',
+        '#title' => $this->t('Plugin'),
+        '#options' => array_map(function(array $plugin_definition) {
+          return $plugin_definition['label'];
+        }, $this->pluginManager->getDefinitions()),
+        '#default_value' => $entity->getPluginId(),
+        '#required' => TRUE,
+        '#ajax' => array(
+          'callback' => '::getPluginContainerFormChild',
+          'wrapper' => 'inmail-plugin',
+        ),
+        '#submit' => array('::submitSelectPlugin'),
+        '#executes_submit_callback' => TRUE,
+        '#limit_validation_errors' => array(array('plugin')),
+      );
+
+      $form['plugin_container']['plugin_submit'] = array(
+        '#type' => 'submit',
+        '#value' => $this->t('Select plugin'),
+        '#submit' => array('::submitSelectPlugin'),
+        '#attributes' => array('class' => array('js-hide')),
+      );
+    }
+
+    // Load plugin instance and form.
+    /** @var \Drupal\Core\Plugin\PluginFormInterface $plugin */
+    if ($entity->getPluginId()) {
+      $plugin = $this->pluginManager->createInstance($entity->getPluginId(), $entity->getConfiguration());
+      $form_state->set('plugin', $plugin);
+      $form['plugin_container']['configuration'] = $plugin->buildConfigurationForm(array(), $form_state);
+    }
+
+    return $form;
+  }
+
+  /**
+   * Determines if the plugin configuration already exists.
+   *
+   * @param string $id
+   *   The plugin configuration ID.
+   *
+   * @return bool
+   *   TRUE if the plugin config exists, FALSE otherwise.
+   */
+  public function exists($id) {
+    return (!is_null($this->storage->load($id)));
+  }
+
+  /**
+   * Submit handler for plugin selection.
+   *
+   * @param array $form
+   *   The form structure.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state.
+   */
+  public function submitSelectPlugin(array $form, FormStateInterface $form_state) {
+    $this->entity = $this->buildEntity($form, $form_state);
+    $form_state->setRebuild();
+  }
+
+  /**
+   * Ajax callback returning the plugin_container part of the form.
+   *
+   * @param array $form
+   *   The form structure.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state.
+   *
+   * @return array
+   *   The plugin_container form part.
+   */
+  public function getPluginContainerFormChild(array $form, FormStateInterface $form_state) {
+    return $form['plugin_container'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    parent::validateForm($form, $form_state);
+
+    /** @var \Drupal\Core\Plugin\PluginFormInterface $plugin */
+    if ($plugin = $form_state->get('plugin')) {
+      $plugin->validateConfigurationForm($form, $form_state);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    parent::submitForm($form, $form_state);
+
+    // Let the plugin update its configuration from the form.
+    /** @var \Drupal\Core\Plugin\PluginFormInterface|\Drupal\Component\Plugin\ConfigurablePluginInterface $plugin */
+    if ($plugin = $form_state->get('plugin')) {
+      $plugin->submitConfigurationForm($form, $form_state);
+    }
+
+    // Copy plugin configuration to the entity for persistence. The reason for
+    // not doing this by overriding copyFormValuesToEntity is that the plugin
+    // submit handler has to happen first.
+    /** @var \Drupal\inmail\Entity\PluginConfigEntity $entity */
+    $entity = $this->getEntity();
+    $entity->setConfiguration($plugin->getConfiguration());
+  }
+
+}
diff --git a/src/MessageProcessor.php b/src/MessageProcessor.php
index f93b080..afe1a0b 100644
--- a/src/MessageProcessor.php
+++ b/src/MessageProcessor.php
@@ -15,7 +15,6 @@ use Drupal\monitoring\Plugin\monitoring\SensorPlugin\EnabledModulesSensorPlugin;
 /**
  * Mail message processor using services to analyze and handle messages.
  *
- * @todo Fetch email by IMAP/POP3 https://www.drupal.org/node/2379889
  * @todo Evaluate the analysis algorithms in D7 Bounce and CiviCRM https://www.drupal.org/node/2379845
  *
  * @ingroup processing
diff --git a/src/Plugin/inmail/Deliverer/DelivererBase.php b/src/Plugin/inmail/Deliverer/DelivererBase.php
new file mode 100644
index 0000000..6eed7c4
--- /dev/null
+++ b/src/Plugin/inmail/Deliverer/DelivererBase.php
@@ -0,0 +1,66 @@
+<?php
+/**
+ * @file
+ * Contains \Plugin\inmail\Deliverer\DelivererBase.
+ */
+
+namespace Drupal\inmail\Plugin\inmail\Deliverer;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\PluginBase;
+
+/**
+ * Base class for mail deliverers.
+ *
+ * This provides dumb implementations for most methods, but leaves ::deliver()
+ * abstract.
+ *
+ * @ingroup deliverer
+ */
+abstract class DelivererBase extends PluginBase implements DelivererInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
+    // Merge with defaults.
+    parent::__construct($configuration + $this->defaultConfiguration(), $plugin_id, $plugin_definition);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLabel() {
+    return $this->pluginDefinition['label'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function calculateDependencies() {
+    return array();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfiguration() {
+    return $this->configuration;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setConfiguration(array $configuration) {
+    $this->configuration = $configuration + $this->defaultConfiguration();
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
+    // No validation by default.
+  }
+
+}
diff --git a/src/Plugin/inmail/Deliverer/DelivererInterface.php b/src/Plugin/inmail/Deliverer/DelivererInterface.php
new file mode 100644
index 0000000..c90ded9
--- /dev/null
+++ b/src/Plugin/inmail/Deliverer/DelivererInterface.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * @file
+ * Contains \Plugin\inmail\Deliverer\DelivererInterface.
+ */
+
+namespace Drupal\inmail\Plugin\inmail\Deliverer;
+
+use Drupal\Component\Plugin\ConfigurablePluginInterface;
+use Drupal\Component\Plugin\PluginInspectionInterface;
+use Drupal\Core\Plugin\PluginFormInterface;
+
+/**
+ * Defines methods for deliverers.
+ *
+ * @ingroup deliverer
+ */
+interface DelivererInterface extends ConfigurablePluginInterface, PluginFormInterface, PluginInspectionInterface {
+
+  /**
+   * Returns the deliverer label.
+   *
+   * @return \Drupal\Core\StringTranslation\TranslationWrapper
+   *   The deliverer label.
+   */
+  public function getLabel();
+
+  /**
+   * Connects to the configured mailbox and delivers new mail.
+   *
+   * @return string[]
+   *   The delivered messages, in complete raw form.
+   */
+  public function deliver();
+
+}
diff --git a/src/Plugin/inmail/Deliverer/ImapDeliverer.php b/src/Plugin/inmail/Deliverer/ImapDeliverer.php
new file mode 100644
index 0000000..5ddb9c9
--- /dev/null
+++ b/src/Plugin/inmail/Deliverer/ImapDeliverer.php
@@ -0,0 +1,207 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\inmail\Deliverer\ImapDeliverer.
+ */
+
+namespace Drupal\inmail\Plugin\inmail\Deliverer;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Logger\LoggerChannelInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\State\StateInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Fetches messages over IMAP.
+ *
+ * @ingroup deliverer
+ *
+ * @Deliverer(
+ *   id = "imap",
+ *   label = @Translation("IMAP")
+ * )
+ */
+class ImapDeliverer extends DelivererBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * Injected Inmail logger channel.
+   *
+   * @var \Drupal\Core\Logger\LoggerChannelInterface
+   */
+  protected $loggerChannel;
+
+  /**
+   * Injected site state.
+   *
+   * @var \Drupal\Core\State\StateInterface
+   */
+  protected $state;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, LoggerChannelInterface $logger_channel, StateInterface $state) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->loggerChannel = $logger_channel;
+    $this->state = $state;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('logger.factory')->get('inmail'),
+      $container->get('state')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deliver() {
+    // Get details from config and connect.
+    // @todo Return noisily if misconfigured or imap missing. Possibly stop retrying.
+    $mailbox_flags = $this->configuration['ssl'] ? '/ssl' : '';
+    $mailbox = '{' . $this->configuration['host'] . ':' . $this->configuration['port'] . $mailbox_flags . '}';
+    $imap_res = imap_open($mailbox, $this->configuration['username'], $this->configuration['password']);
+
+    if (!$imap_res) {
+      // @todo Consider throwing an exception.
+      $this->loggerChannel->error('Deliverer connection failed: @error', ['@error' => implode("\n", imap_errors())]);
+      return array();
+    }
+
+    // Find IDs of unread messages.
+    // @todo Introduce options for message selection:
+    //   - only read UNSEEN and mark unread
+    //   - read all and delete (and optionally expunge)
+    //   - keep track of current UID, read all with higher UID
+    //   In the UI, warn about possible interference with other IMAP connections
+    //   marking messages as read.
+    $unread_ids = imap_search($imap_res, 'UNSEEN') ?: array();
+    $batch_ids = array_splice($unread_ids, 0, $this->configuration['batch_size']);
+
+    // Get the header + body of each message.
+    $raws = array();
+    foreach ($batch_ids as $unread_id) {
+      $raws[] = imap_fetchheader($imap_res, $unread_id) . imap_body($imap_res, $unread_id);
+    }
+
+    // Save number of unread messages.
+    // @todo Create a Monitoring sensor for this state key.
+    $this->state->set('inmail.deliverer.imap.remaining', count($unread_ids));
+
+    // Close resource and return messages.
+    imap_close($imap_res);
+    return $raws;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultConfiguration() {
+    return array(
+      'host' => '',
+      // Standard non-SSL IMAP port as defined by RFC 3501.
+      'port' => 143,
+      'ssl' => FALSE,
+      'username' => '',
+      'password' => '',
+      'batch_size' => '100',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+    $form['info'] = array(
+      '#type' => 'item',
+      '#markup' => $this->t('Please refer to your email provider for the appropriate values for these fields.'),
+    );
+
+    $form['host'] = array(
+      '#type' => 'textfield',
+      '#title' => $this->t('Host'),
+      '#default_value' => $this->configuration['host'],
+    );
+
+    $form['port'] = array(
+      '#type' => 'number',
+      '#title' => $this->t('Port'),
+      '#default_value' => $this->configuration['port'],
+      '#description' => $this->t('The standard port number is 143, or 993 when using SSL.'),
+    );
+
+    $form['ssl'] = array(
+      '#type' => 'checkbox',
+      '#title' => $this->t('Use SSL'),
+      '#default_value' => $this->configuration['ssl'],
+    );
+
+    $form['username'] = array(
+      '#type' => 'textfield',
+      '#title' => $this->t('Username'),
+      '#default_value' => $this->configuration['username'],
+    );
+
+    // Password field cannot have #default_value. To avoid forcing user to
+    // re-enter password with each save, password updating is conditional on
+    // this checkbox.
+    $form['password_update'] = array(
+      '#type' => 'checkbox',
+      '#title' => $this->t('Update password'),
+    );
+
+    $form['password'] = array(
+      '#type' => 'password',
+      '#title' => $this->t('Password'),
+      '#states' => array(
+        'visible' => array(
+          ':input[name=password_update]' => array('checked' => TRUE),
+        ),
+      ),
+    );
+
+    // Always show password field if configuration is new.
+    if ($form_state->getFormObject()->getEntity()->isNew()) {
+      $form['password_update']['#access'] = FALSE;
+      $form['password']['#states']['visible'] = array();
+    }
+
+    $form['batch_size'] = array(
+      '#type' => 'number',
+      '#title' => $this->t('Batch size'),
+      '#default_value' => $this->configuration['batch_size'],
+      '#description' => $this->t('How many messages to fetch on each invocation.'),
+    );
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
+    $configuration = array(
+      'host' => $form_state->getValue('host'),
+      'port' => $form_state->getValue('port'),
+      'ssl' => $form_state->getValue('ssl'),
+      'username' => $form_state->getValue('username'),
+      'batch_size' => $form_state->getValue('batch_size'),
+    ) + $this->getConfiguration();
+
+    // Only update password if "Update password" is checked.
+    if ($form_state->getValue('password_update')) {
+      $configuration['password'] = $form_state->getValue('password');
+    }
+
+    $this->setConfiguration($configuration);
+  }
+
+}
diff --git a/src/Tests/InmailWebTest.php b/src/Tests/InmailWebTest.php
index 8afd11b..931f675 100644
--- a/src/Tests/InmailWebTest.php
+++ b/src/Tests/InmailWebTest.php
@@ -53,6 +53,29 @@ class InmailWebTest extends WebTestBase {
     $this->drupalPostForm(NULL, ['return_path' => ''], 'Save configuration');
     $this->assertText('The configuration options have been saved.');
 
+    // Check Deliverer list.
+    $this->clickLink('Mail deliverers');
+    $this->assertText('There is no Mail deliverer yet.');
+
+    $this->clickLink('Configure new deliverer');
+    $edit = array(
+      'label' => 'Test IMAP Deliverer',
+      'id' => 'test_imap',
+      'plugin' => 'imap',
+    );
+    $this->drupalPostAjaxForm(NULL, $edit, 'plugin');
+    $edit += array(
+      'host' => 'imap.example.com',
+      'username' => 'user',
+      'password' => 'pass',
+    );
+    $this->drupalPostForm(NULL, $edit, 'Save');
+    $this->assertText('Test IMAP Deliverer');
+
+    $this->assertLink('Configure');
+    $this->clickLink('Disable');
+    $this->clickLink('Enable');
+
     // Check Analyzer list.
     $this->clickLink('Message analyzers');
     $this->assertText('Standard DSN Analyzer');
diff --git a/src/Tests/IntegrationTest.php b/src/Tests/IntegrationTest.php
index 7a5bc7e..15b1ef2 100644
--- a/src/Tests/IntegrationTest.php
+++ b/src/Tests/IntegrationTest.php
@@ -64,7 +64,7 @@ class IntegrationTest extends WebTestBase {
     $sent_mails = $this->drupalGetMails();
     $raw = static::generateBounceMessage(array_pop($sent_mails));
     // In reality the message would be passed to the processor through a drush
-    // script or a mail fetcher.
+    // script or a mail deliverer.
     \Drupal::service('inmail.processor')->process($raw);
 
     // Check send state.
