diff --git a/config/install/sms.gateway.config.yml b/config/install/sms.gateway.config.yml
new file mode 100644
index 0000000..aa353e0
--- /dev/null
+++ b/config/install/sms.gateway.config.yml
@@ -0,0 +1,3 @@
+log:
+  enabled: true
+  plugin_id: log
diff --git a/config/schema/sms.schema.yml b/config/schema/sms.schema.yml
index d8ddd63..28d51f5 100644
--- a/config/schema/sms.schema.yml
+++ b/config/schema/sms.schema.yml
@@ -20,11 +20,30 @@ sms.settings:
         - type: string
 
 # Configuration for sms gateways.
-sms.gateway.*:
+sms.gateway.config:
+  type: sequence
+  label: 'All SMS Gateway configuration'
+  sequence:
+    - type: sms.gateway.setting
+
+sms.gateway.setting:
   type: mapping
-  label: 'SMS Gateway settings'
+  label: 'Single SMS Gateway configuration'
   mapping:
-    settings:
+    label:
+      type: label
+      label: 'Human readable gateway label'
+    name:
+      type: string
+      label: 'Gateway machine name'
+    plugin_id:
+      type: string
+      label: 'Gateway plugin type'
+    enabled:
+      type: boolean
+      label: 'Gateway enabled status'
+    custom:
       type: sequence
+      label: 'Additional gateway-specific configuration'
       sequence:
         - type: string
diff --git a/modules/sms_sendtophone/sms_sendtophone.module b/modules/sms_sendtophone/sms_sendtophone.module
index d4a01e5..f071090 100644
--- a/modules/sms_sendtophone/sms_sendtophone.module
+++ b/modules/sms_sendtophone/sms_sendtophone.module
@@ -429,7 +429,7 @@ function sms_sendtophone_form_validate($form, &$form_state) {
  * @see http://drupal.org/node/1354
  */
 function sms_sendtophone_form_submit($form, &$form_state) {
-  if (sms_send($form_state['values']['number'], $form_state['values']['message'], $form_state['values']['gateway'])) {
+  if (sms_send($form_state['values']['number'], $form_state['values']['message'], (array) $form_state['values']['gateway'])) {
     drupal_set_message(t('The message "@message" sent to @number.', array('@message' => $form_state['values']['message'], '@number' => $form_state['values']['number'])));
   }
 }
diff --git a/modules/sms_track/sms_track.module b/modules/sms_track/sms_track.module
index 4d28962..d41b7e6 100644
--- a/modules/sms_track/sms_track.module
+++ b/modules/sms_track/sms_track.module
@@ -8,6 +8,7 @@
  */
 
 use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\sms\Gateway\GatewayInterface;
 use Drupal\sms\Message\SmsMessageInterface;
 use Drupal\views\Plugin\views\query\QueryPluginBase;
 use \Drupal\views\ViewExecutable;
@@ -43,7 +44,9 @@ function sms_track_cron() {
 /**
  * Implements hook_sms_send().
  */
-function sms_track_sms_send(SmsMessageInterface $sms, array $options, $gateway) {
+function sms_track_sms_send(SmsMessageInterface $sms, array $options, GatewayInterface $gateway) {
+  $message = $sms->getMessage();
+  $number = $sms->getRecipients()[0];
   // Tracking.
   // Place a tracking reference on a sent message if we need to.
   if (!empty($options) && !array_key_exists('reference', $options)) {
@@ -56,23 +59,21 @@ function sms_track_sms_send(SmsMessageInterface $sms, array $options, $gateway)
  *
  * @param string $op
  *   Operation string (pre process, process, post process).
- * @param string $number
- *   MSISDN of recipient.
- * @param string $message
- *   SMS message body.
+ * @param \Drupal\sms\Message\SmsMessageInterface $sms
+ *   The sms message object.
  * @param array $options
  *   Additional options array including sender.
- * @param array $gateway
+ * @param \Drupal\sms\Gateway\GatewayInterface $gateway
  *   Gateway array for the active gateway.
  * @param array $result
  *   Result array from the gateway response handler.
  */
-function sms_track_sms_send_process($op, SmsMessageInterface $sms, array $options, $gateway, $result) {
+function sms_track_sms_send_process($op, SmsMessageInterface $sms, array $options, GatewayInterface $gateway, $result) {
   if ($op == 'post process') {
     // Archiving (outgoing == 0)
     $dir = 0;
     $options = (isset($options) && is_array($options))? $options : array();
-    $options['gateway_id'] = $gateway['identifier'];
+    $options['gateway_id'] = $gateway->getIdentifier();
     $options['result']  = $result;
     sms_track_archive_write($dir, implode(',', $sms->getRecipients()), $sms->getMessage(), $options);
   }
@@ -84,10 +85,8 @@ function sms_track_sms_send_process($op, SmsMessageInterface $sms, array $option
  *
  * @param string $op
  *   SMS incoming operation string (pre process, process, post process).
- * @param string $number
- *   MSISDN of sender.
- * @param string $message
- *   SMS message body string.
+ * @param \Drupal\sms\Message\SmsMessageInterface $sms
+ *   The sms message object.
  * @param array $options
  *   Additional options including receiver MSISDN.
  */
@@ -116,7 +115,7 @@ function sms_track_sms_incoming($op, SmsMessageInterface $sms, array $options) {
  * @param array $options
  *   Additional options array.
  */
-function sms_track_sms_receipt($op, $number, $reference, $status, $options) {
+function sms_track_sms_receipt($op, $number, $reference, $status, array $options) {
   if ($op == 'pre process') {
     // Tracking.
     sms_track_update_message($reference, $status);
@@ -136,7 +135,7 @@ function sms_track_sms_receipt($op, $number, $reference, $status, $options) {
  * @param array $options
  *   (optional) an array of additional options.
  */
-function sms_track_archive_write($dir, $number, $message, $options = array()) {
+function sms_track_archive_write($dir, $number, $message, array $options = array()) {
   $user = \Drupal::currentUser();
   $archive_dir = \Drupal::config('sms_track.settings')->get('archive_dir');
   // Query the database for the uid of the user that owns the $number
@@ -159,10 +158,10 @@ function sms_track_archive_write($dir, $number, $message, $options = array()) {
       // Or render a status code from a simple true/false result
       if (! $status) {
         if ($options['result']) {
-          $status = SMS_GW_OK;
+          $status = GatewayInterface::STATUS_OK;
         }
         else {
-          $status = SMS_GW_ERR_OTHER;
+          $status = GatewayInterface::STATUS_ERR_OTHER;
         }
       }
       // Add the author and recipient
@@ -178,7 +177,7 @@ function sms_track_archive_write($dir, $number, $message, $options = array()) {
     if ($archive_dir == SMS_DIR_ALL || $archive_dir == SMS_DIR_IN) {
       $reference = NULL;
       // Inbound message status is always the same.
-      $status = SMS_MSG_STATUS_OK;
+      $status = SmsMessageInterface::STATUS_OK;
       // Add the author and recipient
       $recipient = $user->id();
       $author = ($remote_user) ? $remote_user : 0;
diff --git a/modules/sms_user/sms_user.module b/modules/sms_user/sms_user.module
index 41aaebf..c3421ef 100644
--- a/modules/sms_user/sms_user.module
+++ b/modules/sms_user/sms_user.module
@@ -8,6 +8,7 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Url;
+use Drupal\sms\Gateway\GatewayInterface;
 use Drupal\sms\Message\SmsMessageInterface;
 use Drupal\user\Entity\User;
 use Drupal\user\UserInterface;
@@ -98,21 +99,22 @@ function sms_user_get_uid($number, $status = NULL) {
 
 /**
  * Implements hook_sms_send().
+ *
+ * Aborts sending if the user has opted out or is in sleep mode.
  */
-function sms_user_sms_send_process($step, SmsMessageInterface $sms, array $options, $gateway) {
+function sms_user_sms_send_process($step, SmsMessageInterface $sms, array $options, GatewayInterface $gateway) {
   if ($step == 'pre process') {
-    if (\Drupal::config('sms_user.settings')->get('enable_sleep', 1) && $uid = sms_user_get_uid($sms->getRecipients()[0])) {
+    $number = $sms->getRecipients()[0];
+    if (\Drupal::config('sms_user.settings')->get('enable_sleep', 1) && $uid = sms_user_get_uid($number)) {
       $account = User::load($uid);
 
       if (_sms_user_opted_out($account)) {
-        \Drupal::logger('sms_user')
-          ->info('Message was not sent to @user because user opted out.', array('@user' => $account->getUsername()));
+        \Drupal::logger('sms_user')->info('Message was not sent to @user because user opted out.', array('@user' => $account->getUsername()));
         return FALSE;
       }
       else {
         if (_sms_user_sleep_active($account)) {
-          \Drupal::logger('sms_user')
-            ->info('Message was not sent to @user due to sleep settings.', array('@user' => $account->getUsername()));
+          \Drupal::logger('sms_user')->info('Message was not sent to @user due to sleep settings.', array('@user' => $account->getUsername()));
           return FALSE;
         }
       }
diff --git a/modules/sms_user/src/Tests/SmsUserWebTest.php b/modules/sms_user/src/Tests/SmsUserWebTest.php
index 8725bee..3b7b20f 100644
--- a/modules/sms_user/src/Tests/SmsUserWebTest.php
+++ b/modules/sms_user/src/Tests/SmsUserWebTest.php
@@ -27,7 +27,10 @@ class SmsUserWebTest extends WebTestBase {
    */
   public function testNumberConfirmationAndSmsUserSend() {
     // Set up test default gateway.
-    $this->config('sms.settings')->set('default_gateway', 'test')->save();
+    /** @var \Drupal\sms\Gateway\GatewayManagerInterface $gateway_manager */
+    $gateway_manager = $this->container->get('plugin.manager.sms_gateway');
+    $gateway_manager->setEnabledGateways(['test']);
+    $gateway_manager->setDefaultGateway('test');
     $user = $this->drupalCreateUser(array('receive sms', 'edit own sms number'));
     $this->drupalLogin($user);
 
diff --git a/sms.links.action.yml b/sms.links.action.yml
index eec7b10..f4f6527 100644
--- a/sms.links.action.yml
+++ b/sms.links.action.yml
@@ -4,3 +4,10 @@ sms.carrier_add:
   weight: 1
   appears_on:
     - sms.carrier_admin
+
+sms.gateway_add:
+  title: 'Add Configurable Gateway'
+  route_name: sms.gateway_add
+  weight: 1
+  appears_on:
+    - sms.gateway_admin
diff --git a/sms.module b/sms.module
index 00c18d6..713db07 100644
--- a/sms.module
+++ b/sms.module
@@ -6,7 +6,9 @@
  * sending and receiving SMS messages.
  */
 
+use Drupal\Core\Form\FormState;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\sms\Gateway\GatewayInterface;
 use Drupal\sms\Message\SmsMessage;
 use Drupal\sms\Message\SmsMessageInterface;
 
@@ -26,20 +28,19 @@ define('SMS_MSG_STATUS_NOCREDIT',   SmsMessageInterface::STATUS_NO_CREDIT);
 define('SMS_MSG_STATUS_EXPIRED',    SmsMessageInterface::STATUS_EXPIRED);
 
 // Gateway response codes.
-// 0=Unknown, 2xx=Positive, 4xx=Negative(likely client err), 5xx=Negative(likely gateway err)
-define('SMS_GW_UNKNOWN_STATUS',      0);
-define('SMS_GW_OK',                200);
-define('SMS_GW_ERR_AUTH',          401);
-define('SMS_GW_ERR_INVALID_CALL',  400);
-define('SMS_GW_ERR_NOT_FOUND',     404);
-define('SMS_GW_ERR_MSG_LIMITS',    413);
-define('SMS_GW_ERR_MSG_ROUTING',   502);
-define('SMS_GW_ERR_MSG_QUEUING',   408);
-define('SMS_GW_ERR_MSG_OTHER',     409);
-define('SMS_GW_ERR_SRC_NUMBER',    415);
-define('SMS_GW_ERR_DEST_NUMBER',   416);
-define('SMS_GW_ERR_CREDIT',        402);
-define('SMS_GW_ERR_OTHER',         500);
+define('SMS_GW_UNKNOWN_STATUS',    GatewayInterface::STATUS_UNKNOWN);
+define('SMS_GW_OK',                GatewayInterface::STATUS_OK);
+define('SMS_GW_ERR_AUTH',          GatewayInterface::STATUS_ERR_AUTH);
+define('SMS_GW_ERR_INVALID_CALL',  GatewayInterface::STATUS_ERR_INVALID_CALL);
+define('SMS_GW_ERR_NOT_FOUND',     GatewayInterface::STATUS_ERR_NOT_FOUND);
+define('SMS_GW_ERR_MSG_LIMITS',    GatewayInterface::STATUS_ERR_MSG_LIMITS);
+define('SMS_GW_ERR_MSG_ROUTING',   GatewayInterface::STATUS_ERR_MSG_ROUTING);
+define('SMS_GW_ERR_MSG_QUEUING',   GatewayInterface::STATUS_ERR_MSG_QUEUING);
+define('SMS_GW_ERR_MSG_OTHER',     GatewayInterface::STATUS_ERR_MSG_OTHER);
+define('SMS_GW_ERR_SRC_NUMBER',    GatewayInterface::STATUS_ERR_SRC_NUMBER);
+define('SMS_GW_ERR_DEST_NUMBER',   GatewayInterface::STATUS_ERR_DEST_NUMBER);
+define('SMS_GW_ERR_CREDIT',        GatewayInterface::STATUS_ERR_CREDIT);
+define('SMS_GW_ERR_OTHER',         GatewayInterface::STATUS_ERR_OTHER);
 
 // Carrier status.
 define('SMS_CARRIER_DEFAULT', 0);
@@ -128,148 +129,78 @@ function sms_incoming($number, $message, array $options = array()) {
 /**
  * Handles responses to message transactions.
  *
- * Allows gateways modules to pass message receipts and other responses to
- * messages in a standard format for processing, and provides a basic set of
- * status codes for common code handling.
- *
- * Allowed message status codes are defined as constants in this module.
- *
- * The gateway status code and message should be provided in the $options array
- * as 'gateway_message_status' and 'gateway_message_status_text'.
- *
- * @param string $number
- *   The sender's mobile number.
- * @param string $reference
- *   Unique message reference code, as provided when message is sent.
- * @param int $message_status
- *   (optional) An SMS Framework message status code, per the defined constants.
- *    Defaults to SMS_GW_UNKNOWN_STATUS.
- * @param array $options
- *   (optional) Extended options passed by the receipt receiver.
+ * @see \Drupal\sms\Gateway\SmsProviderInterface::receipt
  *
  * @deprecated use \Drupal\sms\Provider\SmsProviderInterface::receipt() instead.
  */
-function sms_receipt($number, $reference, $message_status = SMS_GW_UNKNOWN_STATUS, $options = array()) {
+function sms_receipt($number, $reference, $message_status = GatewayInterface::STATUS_UNKNOWN, $options = array()) {
   \Drupal::service('sms_provider.default')->receipt($number, $reference, $message_status, $options);
 }
 
 /**
  * Returns the current default gateway.
+ *
+ * @return \Drupal\sms\Gateway\GatewayInterface
  */
 function sms_default_gateway() {
-  return sms_gateways('gateway', sms_default_gateway_id());
+  return \Drupal::service('plugin.manager.sms_gateway')->getDefaultGateway();
 }
 
 /**
- * Returns the current default gateway machine name.
+ * Returns the identifier for the current default gateway.
+ *
+ * @return string
  */
 function sms_default_gateway_id() {
   return \Drupal::config('sms.settings')->get('default_gateway');
 }
 
 /**
- * Implements hook_gateway_info().
- */
-function sms_gateway_info() {
-  return array(
-    'log' => array(
-      'name' => t('Log only'),
-      'send' => 'sms_send_log',
-    ),
-  );
-}
-
-/**
- * Logs sms message.
- *
- * This is a rudimentary implementation of an sms gateway by simply logging the
- * message to watchdog.
- *
- * @param \Drupal\sms\Message\SmsMessageInterface $sms
- *   The message that was sent.
- * @param array $options
- *   An associative array of options passed to gateway.
- *
- * @return array
- *   An array containing one key:
- *   - status: true.
- */
-function sms_send_log(SmsMessageInterface $sms, $options) {
-  \Drupal::logger('sms')->info('SMS message sent to %number with the text: @message', array('%number' => implode(',', $sms->getRecipients()), '@message' => $sms->getMessage()));
-  return array('status' => TRUE);
-}
-
-
-/**
- * SMS gateway menutitle callback.
- */
-function sms_admin_gateway_title($gateway_id) {
-  $gateway = sms_gateways('gateway', $gateway_id);
-  return sprintf('%s gateway', $gateway['name']);
-}
-
-/**
  * Gets a list of all gateways.
  *
  * @param string $op
- *   (optional) The format in which to return the list. When set to 'gateway' or 'name',
- *   only the specified gateway is returned. When set to 'gateways' or 'names',
- *   all gateways are returned. Defaults to 'gateways'.
+ *   (optional) The format in which to return the list. When set to 'gateway' or
+ *   'name', only the specified gateway is returned. When set to 'gateways' or
+ *   'names', all gateways are returned. Defaults to 'gateways'.
+ *
  * @param string $gateway
  *   (optional) A gateway identifier string that indicates the gateway to return.
  *   Leave at default value (NULL) to return all gateways.
  *
- * @return array|string|NULL
+ * @return array|\Drupal\sms\Gateway\GatewayInterface
  *   Either an array of all gateways or a single gateway, in a variable format.
+ *
+ * @deprecated as of 8.x-1.x. Use @code GatewayManager @endcode methods instead.
+ * @code
+ *   Drupal::service('plugin.manager.sms_gateway')->getAvailableGateways();
+ *   Drupal::service('plugin.manager.sms_gateway')->getEnabledGateways();
+ *   Drupal::service('plugin.manager.sms_gateway')->getDefaultGateway();
+ *   Drupal::service('plugin.manager.sms_gateway')->getGateway($name);
+ * @endcode
  */
 function sms_gateways($op = 'gateways', $gateway = NULL) {
-  list($_gateways, $_names) = _gateways_build();
+  /** @var \Drupal\sms\Gateway\GatewayManagerInterface $gateway_manager */
+  $gateway_manager = \Drupal::service('plugin.manager.sms_gateway');
 
   switch ($op) {
     case 'gateway':
-      if (isset($_gateways[$gateway])) {
-        $return = $_gateways[$gateway];
-        $return['identifier'] = $gateway;
-        return $return;
-      }
-      else {
-        return NULL;
-      }
+      return $gateway_manager->getGateway($gateway) ? : array();
     case 'names':
+      $_names = array();
+      foreach ($gateway_manager->getAvailableGateways() as $id => $gw) {
+        $_names[$id] = $gw->getLabel();
+      }
       return $_names;
     case 'name':
-      return isset($_names[$gateway]) ? $_names[$gateway] : NULL;
+      $gw = $gateway_manager->getGateway($gateway);
+      return ($gw) ? $gw->getLabel() : '';
     case 'gateways':
     default:
-      return $_gateways;
+      return $gateway_manager->getAvailableGateways();
   }
 }
 
 /**
- * Helper function to get gateway definitions in hook_gateway_info.
- *
- * @return array
- *   A array of gateway definitions from hook_gateway_info().
- */
-function _gateways_build() {
-  // @todo Implement caching here
-  $_gateways = array();
-  $_names = array();
-
-  $gateway_array = \Drupal::moduleHandler()->invokeAll('gateway_info');
-  foreach ($gateway_array as $identifier => $info) {
-    $settings = \Drupal::config('sms.gateway.' . $identifier)->get('settings');
-    $info['configuration'] = isset($settings) ? $settings : array();
-    $_gateways[$identifier] = $info;
-    $_names[$identifier] = $info['name'];
-  }
-
-  asort($_names);
-
-  return array($_gateways, $_names);
-}
-
-/**
  * Form builder for send sms form.
  *
  * Generates a SMS sending form and adds gateway defined elements. The form
@@ -293,10 +224,9 @@ function sms_send_form($required = FALSE) {
   );
 
   // Add gateway defined fields
-  if (!empty($gateway['send form']) && function_exists($gateway['send form'])) {
-    $form['gateway']['#tree'] = TRUE;
-    $form['gateway'] = array_merge($gateway['send form']($required), $form['gateway']);
-  }
+  $form_state = new FormState();
+  $form['gateway']['#tree'] = TRUE;
+  $form['gateway'] = array_merge($gateway->sendForm($form, $form_state), $form['gateway']);
 
   return $form;
 }
@@ -560,10 +490,8 @@ function sms_validate_number(&$number, array $options = array()) {
   else {
     $gateway = sms_default_gateway();
   }
-  if (isset($gateway['validate number']) && function_exists($gateway['validate number'])) {
-    if ($error = $gateway['validate number']($number, $options)) {
-      $errors += (array) $error;
-    }
+  if ($error = $gateway->validateNumbers([$number], $options)) {
+    $errors += (array) $error;
   }
 
   return $errors;
diff --git a/sms.routing.yml b/sms.routing.yml
index bd2ef6c..099d5ff 100644
--- a/sms.routing.yml
+++ b/sms.routing.yml
@@ -14,6 +14,14 @@ sms.gateway_admin:
   requirements:
     _permission: 'administer smsframework'
 
+sms.gateway_add:
+  path: '/admin/config/smsframework/gateways/add'
+  defaults:
+    _form: '\Drupal\sms\Form\GatewayAddForm'
+    _title: 'Add new gateway configuration'
+  requirements:
+    _permission: 'administer smsframework'
+
 sms.gateway_config:
   path: '/admin/config/smsframework/gateways/{gateway_id}'
   defaults:
@@ -62,3 +70,10 @@ sms.bootstrap_admin:
     _title: 'Incoming SMS bootstrap'
   requirements:
     _permission: 'administer smsframework'
+
+sms.delivery_report:
+  path: '/sms/deliveryreport/{gateway_id}'
+  defaults:
+    _controller: '\Drupal\sms\DeliveryReportController::acknowledgeDelivery'
+  requirements:
+    _access: 'TRUE'
diff --git a/sms.services.yml b/sms.services.yml
index 7f49ff4..39f9257 100644
--- a/sms.services.yml
+++ b/sms.services.yml
@@ -1,4 +1,8 @@
 services:
   sms_provider.default:
     class: Drupal\sms\Provider\DefaultSmsProvider
-    arguments: ['@module_handler']
+    arguments: ['@plugin.manager.sms_gateway', '@module_handler']
+
+  plugin.manager.sms_gateway:
+    class: Drupal\sms\Gateway\GatewayManager
+    arguments: ['@container.namespaces', '@config.factory', '@cache.discovery', '@module_handler']
diff --git a/src/Annotation/SmsGateway.php b/src/Annotation/SmsGateway.php
new file mode 100644
index 0000000..a2293f7
--- /dev/null
+++ b/src/Annotation/SmsGateway.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\sms\Annotation\SmsGateway
+ */
+
+namespace Drupal\sms\Annotation;
+
+use Drupal\Component\Annotation\Plugin;
+
+/**
+ * Defines SmsGateway Annotation object.
+ *
+ * @Annotation
+ */
+class SmsGateway extends Plugin {
+
+  /**
+   * The machine name of the sms gateway.
+   *
+   * @var string
+   */
+  protected $id;
+
+  /**
+   * Translated user-readable label.
+   * @var string
+   */
+  protected $label;
+
+  /**
+   * A boolean flag to determine if the gateway is configurable.
+   *
+   * @var bool
+   */
+  protected $configurable;
+
+}
diff --git a/src/DeliveryReportController.php b/src/DeliveryReportController.php
index 001dfe2..fa54056 100644
--- a/src/DeliveryReportController.php
+++ b/src/DeliveryReportController.php
@@ -19,6 +19,13 @@ use Symfony\Component\HttpFoundation\Response;
 class DeliveryReportController implements ContainerInjectionInterface {
 
   /**
+   * The gateway manager.
+   *
+   * @var \Drupal\sms\Gateway\GatewayManagerInterface
+   */
+  protected $gatewayManager;
+
+  /**
    * The request stack.
    *
    * @var \Symfony\Component\HttpFoundation\RequestStack
@@ -31,7 +38,8 @@ class DeliveryReportController implements ContainerInjectionInterface {
    * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
    *   The request stack.
    */
-  public function __construct(RequestStack $request_stack) {
+  public function __construct(GatewayManagerInterface $gateway_manager, RequestStack $request_stack) {
+    $this->gatewayManager = $gateway_manager;
     $this->requestStack = $request_stack;
   }
 
@@ -44,14 +52,8 @@ class DeliveryReportController implements ContainerInjectionInterface {
    * @return \Symfony\Component\HttpFoundation\Response
    */
   public function acknowledgeDelivery($gateway_id) {
-    $gateway = sms_gateways('gateway', $gateway_id);
-    if (is_callable($gateway['delivery report'])) {
-      $acknowledgement = $gateway['delivery report']($this->requestStack->getCurrentRequest());
-      return new Response($acknowledgement);
-    }
-    // Because we don't want a 500 going to the SMS gateway server we return an
-    // empty response.
-    return new Response('');
+    $acknowledgement = $this->gatewayManager->getGateway($gateway_id)->deliveryReport($this->requestStack->getCurrentRequest());
+    return new Response($acknowledgement);
   }
 
   /**
@@ -59,6 +61,7 @@ class DeliveryReportController implements ContainerInjectionInterface {
    */
   public static function create(ContainerInterface $container) {
     return new static(
+      $container->get('plugin.manager.sms_gateway'),
       $container->get('request_stack')
     );
   }
diff --git a/src/Form/GatewayAddForm.php b/src/Form/GatewayAddForm.php
new file mode 100644
index 0000000..850483b
--- /dev/null
+++ b/src/Form/GatewayAddForm.php
@@ -0,0 +1,106 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\sms\Form\GatewayCreateForm
+ */
+
+namespace Drupal\sms\Form;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\sms\Gateway\GatewayManagerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+class GatewayAddForm extends FormBase {
+
+  /**
+   * The gateway manager.
+   *
+   * @var \Drupal\sms\Gateway\GatewayManagerInterface
+   */
+  protected $gatewayManager;
+
+  /**
+   * Constructor.
+   *
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The configuration factory.
+   * @param \Drupal\sms\Gateway\GatewayManagerInterface $gateway_manager
+   *   The gateway manager
+   */
+  public function __construct(ConfigFactoryInterface $config_factory, GatewayManagerInterface $gateway_manager) {
+    $this->setConfigFactory($config_factory);
+    $this->gatewayManager = $gateway_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('config.factory'),
+      $container->get('plugin.manager.sms_gateway')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'gateway_create_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $form['label'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Gateway name'),
+    ];
+    $form['name'] = [
+      '#type' => 'machine_name',
+      '#title' => '',
+      '#machine_name' => [
+        'exists' => [$this, 'gatewayExists'],
+        'source' => ['label'],
+      ],
+    ];
+    $form['plugin_id'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Gateway type'),
+      // List plugins that are configurable so user can choose.
+      '#options' => array_filter(array_map(function ($definition) {
+        return $definition['configurable'] ? $definition['label'] : FALSE;
+      }, $this->gatewayManager->getGatewayPlugins())),
+      '#required' => TRUE,
+    ];
+
+    $form['submit'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Create'),
+    ];
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    // Create the gateway and redirect to the configure form.
+    $configuration = $form_state->cleanValues()->getValues();
+    $this->gatewayManager->addGateway($configuration['plugin_id'], $configuration);
+    $form_state->setRedirect('sms.gateway_config', ['gateway_id' => $configuration['name']]);
+  }
+
+  /**
+   * Helper function for machine_name form element.
+   */
+  public function gatewayExists($machine_name) {
+    return (bool) $this->gatewayManager->getGateway($machine_name);
+  }
+
+}
diff --git a/src/Form/GatewayConfigForm.php b/src/Form/GatewayConfigForm.php
index 3b74719..7e808a1 100644
--- a/src/Form/GatewayConfigForm.php
+++ b/src/Form/GatewayConfigForm.php
@@ -2,13 +2,16 @@
 
 /**
  * @file
- * Contains GatewayConfigForm class
+ * Contains \Drupal\sms\Form\GatewayConfigForm
  */
 
 namespace Drupal\sms\Form;
 
+use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Form\ConfigFormBase;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\sms\Gateway\GatewayManagerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 
 /**
@@ -18,6 +21,32 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  * form more streamlined
  */
 class GatewayConfigForm extends ConfigFormBase {
+
+  /**
+   * The gateway manager.
+   *
+   * @var \Drupal\sms\Gateway\GatewayManagerInterface
+   */
+  protected $gatewayManager;
+
+  /**
+   * Creates new Gateway configuration form.
+   */
+  public function __construct(ConfigFactoryInterface $config_factory, GatewayManagerInterface $gateway_manager) {
+    $this->setConfigFactory($config_factory);
+    $this->gatewayManager = $gateway_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('config.factory'),
+      $container->get('plugin.manager.sms_gateway')
+    );
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -29,18 +58,27 @@ class GatewayConfigForm extends ConfigFormBase {
    * {@inheritdoc}
    */
   public function buildForm(array $form, FormStateInterface $form_state, $gateway_id = NULL) {
-    $gateway = sms_gateways('gateway', $gateway_id);
-    if ($gateway && !empty($gateway['configure form']) && function_exists($gateway['configure form'])) {
-      $form = $gateway['configure form']($gateway['configuration']);
-      $form['#title'] = $this->t('@gateway configuration', array('@gateway' => $gateway['name']));
-
+    $gateway = $this->gatewayManager->getGateway($gateway_id);
+    if ($gateway && $gateway->isConfigurable()) {
+      $form['title'] = [
+        '#type' => 'item',
+        '#title' => $gateway->getLabel(),
+        '#markup' => '(' . $gateway->getName() . ')',
+      ];
+      $form['enabled'] = [
+        '#type' => 'checkbox',
+        '#title' => $this->t('Enable this gateway'),
+        '#default_value' => $gateway->isEnabled(),
+      ];
+      $form = $gateway->buildConfigurationForm($form, $form_state);
+      $form['#title'] = $this->t('@gateway configuration', array('@gateway' => $gateway->getLabel()));
       $form['submit'] = array(
         '#type' => 'submit',
-        '#value' => $this->t('Save'),
+        '#value' => $this->t('Save configuration'),
       );
-      $form['gateway'] = array(
+      $form['gateway_id'] = array(
         '#type' => 'value',
-        '#value' => $gateway,
+        '#value' => $gateway->getIdentifier(),
       );
   
       return $form;
@@ -55,24 +93,24 @@ class GatewayConfigForm extends ConfigFormBase {
    */
   public function validateForm(array &$form, FormStateInterface $form_state) {
     // Pass validation to gateway.
-    $function = $form_state->getValue(['gateway', 'configure form']) . '_validate';
-    if (function_exists($function)) {
-      $function($form, $form_state);
-    }
+    $gateway = $this->gatewayManager->getGateway($form_state->getValue('gateway_id'));
+    $gateway->validateConfigurationForm($form, $form_state);
   }
 
   /**
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
-    $gateway = $form_state->getValue('gateway');
-    // Remove unnecessary values.
-    $form_state->cleanValues();
-    $form_state->unsetValue('gateway');
-
-    $this->configFactory()->getEditable('sms.gateway.' . $gateway['identifier'])
-      ->set('settings' , $form_state->getValues())
-      ->save();
+    // Call the gateway submission callback before saving configuration.
+    $gateway = $this->gatewayManager->getGateway($form_state->getValue('gateway_id'));
+    $gateway->setEnabled($form_state->getValue('enabled'));
+    $form_state
+      ->unsetValue('title')
+      ->unsetValue('enabled')
+      ->unsetValue('gateway_id')
+      ->cleanValues();
+    $gateway->submitConfigurationForm($form, $form_state);
+    $this->gatewayManager->saveGateway($gateway);
     drupal_set_message($this->t('The gateway settings have been saved.'));
     $form_state->setRedirect('sms.gateway_admin');
   }
@@ -81,8 +119,7 @@ class GatewayConfigForm extends ConfigFormBase {
    * Title callback fo the menu
    */
   public function getTitle($gateway_id) {
-    $gateway = sms_gateways('gateway', $gateway_id);
-    return sprintf('%s gateway', $gateway['name']);
+    return $this->t('@name gateway', ['@name' => $this->gatewayManager->getGateway($gateway_id)->getLabel()]);
   }
 
   /**
diff --git a/src/Form/GatewayDefaultForm.php b/src/Form/GatewayDefaultForm.php
index 3856657..1f08232 100644
--- a/src/Form/GatewayDefaultForm.php
+++ b/src/Form/GatewayDefaultForm.php
@@ -2,20 +2,49 @@
 
 /**
  * @file
- * Contains GatewayDefaultForm class
+ * Contains \Drupal\sms\Form\GatewayDefaultForm
  */
 
 namespace Drupal\sms\Form;
 
 use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Form\ConfigFormBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Url;
+use Drupal\sms\Gateway\GatewayManagerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Provides a configuration form for setting the default gateway.
  */
 class GatewayDefaultForm extends ConfigFormBase {
+
+  /**
+   * The gateway manager.
+   *
+   * @var \Drupal\sms\Gateway\GatewayManagerInterface
+   */
+  protected $gatewayManager;
+
+  /**
+   * Creates new Gateway default selection form.
+   */
+  public function __construct(ConfigFactoryInterface $config_factory, GatewayManagerInterface $gateway_manager) {
+    $this->setConfigFactory($config_factory);
+    $this->gatewayManager = $gateway_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('config.factory'),
+      $container->get('plugin.manager.sms_gateway')
+    );
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -28,12 +57,14 @@ class GatewayDefaultForm extends ConfigFormBase {
    */
   public function buildForm(array $form, FormStateInterface $form_state) {
     $form = parent::buildForm($form, $form_state);
-    $gateways = sms_gateways();
-    $default = sms_default_gateway_id();
+    /** @var \Drupal\sms\Gateway\GatewayInterface[] $gateways */
+    $gateways = $this->gatewayManager->getAvailableGateways();
+    $default = $this->gatewayManager->getDefaultGateway();
 
     $form['gateways'] = array(
       '#type' => 'table',
       '#header' => array(
+        $this->t('Enabled'),
         $this->t('Default'),
         $this->t('Name'),
         array(
@@ -47,41 +78,79 @@ class GatewayDefaultForm extends ConfigFormBase {
     );
     foreach ($gateways as $identifier => $gateway) {
       $form['gateways'][$identifier] = array(
+        'enabled' => [
+          '#type' => 'checkbox',
+          '#default_value' => $gateway->isEnabled(),
+          // The checkbox should be disabled if this gateway is the default.
+          // @todo Needs tests.
+          '#states' => [
+            'disabled' => [
+              ':input[name="default"]' => ['value' => $identifier],
+            ],
+          ],
+        ],
         'default' => [
           '#name' => 'default',
           '#type' => 'radio',
-          '#default_value' => $default,
+          '#default_value' => ($default && $default->getName() ==  $identifier),
           '#return_value' => $identifier,
+          // The radio button should be disabled if this gateway not enabled.
+          // @todo Needs tests.
+          '#states' => [
+            'disabled' => [
+              ':input[name="gateways[' . $identifier . '][enabled]"]' => ['checked' => FALSE],
+            ],
+          ],
+        ],
+        'name' => [
+          '#markup' => SafeMarkup::checkPlain($gateway->getLabel()),
         ],
-        'name' => ['#markup' => SafeMarkup::checkPlain($gateway['name'])],
       );
-      if (isset($gateway['configure form']) && function_exists($gateway['configure form'] )) {
-        $form['gateways'][$identifier]['configure'] = ['#markup' => $this->l($this->t('configure'), Url::fromRoute('sms.gateway_config', ['gateway_id' => $identifier]))];
+      if ($gateway->isConfigurable()) {
+        $form['gateways'][$identifier]['configure'] = [
+          '#type' => 'link',
+          '#title' => $this->t('configure'),
+          '#url' => Url::fromRoute('sms.gateway_config', ['gateway_id' => $identifier]),
+        ];
       }
       else {
-        $form['gateways'][$identifier]['configure'] = array('#markup' => $this->t('No configuration options'));
+        $form['gateways'][$identifier]['configure'] = [
+          '#markup' => $this->t('No configuration options')
+        ];
       }
     }
-    $form['actions']['submit']['#value'] = $this->t('Set default gateway');
+    $form['actions']['submit']['#value'] = $this->t('Save settings');
     return $form;
   }
 
   public function validateForm(array &$form, FormStateInterface $form_state) {
-    $form_state->setValue('default', $form_state->getUserInput()['default']);
+    if (empty($form_state->getUserInput()['default'])) {
+      $form_state->setErrorByName('default', $this->t('Default gateway must be set.'));
+    }
+    else {
+      $form_state->setValue('default', $form_state->getUserInput()['default']);
+    }
   }
   
   /**
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
+    // Set the enabled status of the gateways.
+    $new_default = $form_state->getValue('default');
+    foreach ($form_state->getValue('gateways') as $identifier => $config) {
+      $gateway = $this->gatewayManager->getGateway($identifier);
+      // Selected default gateway is automatically enabled.
+      if ($gateway->getName() === $new_default) {
+        $config['enabled'] = TRUE;
+      }
+      $gateway->setEnabled($config['enabled']);
+      $this->gatewayManager->saveGateway($gateway);
+    }
     // Process form submission to set the default gateway.
-    $default = $form_state->getValue('default');
-    if (sms_gateways('name', $default)) {
+    if ($this->gatewayManager->getGateway($new_default)) {
       drupal_set_message($this->t('Default gateway updated.'));
-
-      $this->config('sms.settings')
-        ->set('default_gateway', $default)
-        ->save();
+      $this->gatewayManager->setDefaultGateway($new_default);
     }
   }
 
diff --git a/src/Gateway/GatewayBase.php b/src/Gateway/GatewayBase.php
new file mode 100644
index 0000000..75b8cce
--- /dev/null
+++ b/src/Gateway/GatewayBase.php
@@ -0,0 +1,180 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\sms\Gateway\GatewayBase
+ */
+
+namespace Drupal\sms\Gateway;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\PluginBase;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Base class for sms gateway plugins.
+ */
+abstract class GatewayBase extends PluginBase implements GatewayInterface {
+
+  /**
+   * The watchdog logger for this gateway.
+   *
+   * @var \Psr\Log\LoggerInterface.
+   */
+  protected $logger;
+
+  /**
+   * Construct a new SmsGateway plugin
+   *
+   * @param array $configuration
+   *   The configuration to use and build the sms gateway.
+   * @param string $plugin_id
+   *   The gateway id.
+   * @param mixed $plugin_definition
+   *   The gateway plugin definition.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->configuration = $this->configuration + $this->defaultConfiguration();
+    $this->configuration['custom'] = [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIdentifier() {
+    return $this->configuration['name'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getName() {
+    return $this->configuration['name'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLabel() {
+    return $this->configuration['label'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfiguration() {
+    return $this->configuration;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setConfiguration(array $configuration) {
+    $this->configuration = $configuration;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCustomConfiguration() {
+    return $this->configuration['custom'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setCustomConfiguration($configuration) {
+    $this->configuration['custom'] = $configuration;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultConfiguration() {
+    return [
+      'plugin_id' => $this->pluginDefinition['id'],
+      'name' => $this->pluginDefinition['id'],
+      'label' => (string) $this->pluginDefinition['label'],
+      'enabled' => FALSE,
+      'custom' => [
+        'use_ssl' => FALSE,
+        'send_method' => 'HTTP_POST',
+      ]
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isConfigurable() {
+    return $this->pluginDefinition['configurable'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
+    return;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
+    return;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function sendForm(array &$form, FormStateInterface $form_state) {
+    return array();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isEnabled() {
+    return isset($this->configuration['enabled']) ? $this->configuration['enabled'] : FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setEnabled($enabled = TRUE) {
+    $this->configuration['enabled'] = (bool) $enabled;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function calculateDependencies() {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateNumbers(array $numbers, array $options = array()) {
+    return [];
+  }
+
+  /**
+   * Gets the Psr logger for this gateway.
+   */
+  protected function logger() {
+    if (!isset($this->logger)) {
+      $this->logger = \Drupal::logger($this->getName());
+    }
+    return $this->logger;
+  }
+
+}
diff --git a/src/Gateway/GatewayInterface.php b/src/Gateway/GatewayInterface.php
new file mode 100644
index 0000000..9d3aea8
--- /dev/null
+++ b/src/Gateway/GatewayInterface.php
@@ -0,0 +1,242 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\sms\Gateway\GatewayInterface
+ */
+
+namespace Drupal\sms\Gateway;
+
+use Drupal\Component\Plugin\ConfigurablePluginInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\PluginFormInterface;
+use Drupal\sms\Message\SmsMessageInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Default implementation of sms gateway plugin
+ */
+interface GatewayInterface extends ConfigurablePluginInterface, PluginFormInterface {
+
+  // Gateway response codes
+  // 0=Unknown, 2xx=Positive, 4xx=Negative(likely client err), 5xx=Negative(likely gateway err)
+
+  /**
+   * Status Unknown.
+   *
+   * A gateway should return this status to indicate unknown status.
+   */
+  const STATUS_UNKNOWN = 0;
+
+  /**
+   * Status OK.
+   *
+   * A gateway should return this status to indicate message successfully sent.
+   */
+  const STATUS_OK = 200;
+
+  /**
+   * Authentication error.
+   *
+   * A gateway should return this status to indicate authentication error.
+   */
+  const STATUS_ERR_AUTH = 401;
+
+  /**
+   * Invalid Call.
+   *
+   * A gateway should return this status to indicate invalid call attempt.
+   */
+  const STATUS_ERR_INVALID_CALL = 400;
+
+  /**
+   * Gateway endpoint not found.
+   *
+   * A gateway should return this status to indicate gateway endpoint not found.
+   */
+  const STATUS_ERR_NOT_FOUND = 404;
+
+  /**
+   * Message limits exceeded.
+   *
+   * A gateway should return this status to indicate message limits exceeded.
+   */
+  const STATUS_ERR_MSG_LIMITS = 413;
+
+  /**
+   * Routing error.
+   *
+   * A gateway should return this status to indicate message routing error.
+   */
+  const STATUS_ERR_MSG_ROUTING = 502;
+
+  /**
+   * Message queuing error.
+   *
+   * A gateway should return this status to indicate message queuing error.
+   */
+  const STATUS_ERR_MSG_QUEUING = 408;
+
+  /**
+   * Other message error.
+   *
+   * A gateway should return this status to indicate other message error.
+   */
+  const STATUS_ERR_MSG_OTHER = 409;
+
+  /**
+   * Source number or id error.
+   *
+   * A gateway should return this status to indicate sender number or id error.
+   */
+  const STATUS_ERR_SRC_NUMBER = 415;
+
+  /**
+   * Destination number error.
+   *
+   * A gateway should return this status to indicate destination number error.
+   */
+  const STATUS_ERR_DEST_NUMBER = 416;
+
+  /**
+   * Credit error.
+   *
+   * A gateway should return this status to indicate insufficient credit error.
+   */
+  const STATUS_ERR_CREDIT = 402;
+
+  /**
+   * Other error.
+   *
+   * A gateway should return this status to indicate a known error condition but
+   * not mappable to any equivalent above.
+   */
+  const STATUS_ERR_OTHER = 500;
+
+  /**
+   * Use get
+   * Connection methods
+   */
+  const HTTP_GET_SPLIT = 0;
+  const HTTP_GET = 1;
+  const HTTP_POST = 2;
+  const SMPP = 3;
+
+  /**
+   * Sends an sms and invokes the corresponding sms receipt method.
+   *
+   * @param \Drupal\sms\Message\SmsMessageInterface $sms
+   *   The sms to be sent.
+   * @param array $options
+   *   Options to be applied while processing this sms.
+   *
+   * @return \Drupal\sms\Message\SmsMessageResultInterface
+   *   The result of the sms messaging operation.
+   */
+  public function send(SmsMessageInterface $sms, array $options);
+
+  /**
+   * Returns the credit balance available on this gateway.
+   *
+   * @return number
+   */
+  public function balance();
+
+  /**
+   * Handles delivery reports and invokes the corresponding sms_receipt method.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request
+   *   Request object containing the delivery report in raw format.
+   */
+  public function deliveryReport(Request $request);
+
+  /**
+   * Gets the last error message from the gateway.
+   *
+   * @return array
+   *   An array of values containing the following:
+   *   - code: the error code.
+   *   - message: the error message.
+   */
+  public function getError();
+
+  /**
+   * Gets the machine  name of the gateway.
+   *
+   * @return string
+   */
+  public function getIdentifier();
+
+  /**
+   * Gets the readable name of the gateway.
+   *
+   * @return string
+   */
+  public function getName();
+
+  /**
+   * Gets the user-readable translated name of the gateway.
+   *
+   * @return string
+   */
+  public function getLabel();
+
+  /**
+   * Returns a boolean to show if the gateway is configurable.
+   *
+   * @return bool
+   *   TRUE if the form is configurable, FALSE if not.
+   */
+  public function isConfigurable();
+
+  /**
+   * Returns a form to be appended to the send form.
+   *
+   * @param array $form
+   *   The parent form array.
+   * @param array $form_state
+   *   Form state.
+   * @returns array
+   *   The form for additional gateway-specific sending options.
+   */
+  public function sendForm(array &$form, FormStateInterface $form_state);
+
+  /**
+   * Gets the enabled status of the gateway.
+   *
+   * @return bool
+   */
+  public function isEnabled();
+
+  /**
+   * Sets the enabled status of the gateway.
+   *
+   * @param bool $status
+   *   The enabled status.
+   */
+  public function setEnabled($status = TRUE);
+
+  /**
+   * Gets the plugin-specific configuration for this gateway.
+   */
+  public function getCustomConfiguration();
+
+  /**
+   * Sets the plugin-specific configuration for this gateway.
+   */
+  public function setCustomConfiguration($configuration);
+
+  /**
+   * Carry out gateway-specific number validation.
+   *
+   * @param array $numbers
+   *   The list of phone numbers to be validated.
+   * @param array $options
+   *   Options to be considered for validation.
+   *
+   * @return array
+   *   An array containing an error message for each validation failure.
+   */
+  public function validateNumbers(array $numbers, array $options = array());
+
+}
diff --git a/src/Gateway/GatewayManager.php b/src/Gateway/GatewayManager.php
new file mode 100644
index 0000000..105b633
--- /dev/null
+++ b/src/Gateway/GatewayManager.php
@@ -0,0 +1,220 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\sms\Gateway\GatewayManager
+ */
+
+namespace Drupal\sms\Gateway;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Config\ConfigFactory;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Plugin\DefaultPluginManager;
+
+/**
+ * Manages SMS gateways implemented using AnnotatedClassDiscovery
+ */
+class GatewayManager extends DefaultPluginManager implements GatewayManagerInterface {
+
+  /**
+   * Configuration factory for this gateway manager.
+   *
+   * @var \Drupal\Core\Config\ConfigFactory
+   */
+  protected $config;
+
+  /**
+   * List of gateways managed by this gateway manager.
+   *
+   * @var \Drupal\sms\Gateway\GatewayInterface[]
+   */
+  protected $gateways;
+
+  /**
+   * List of human-readable names for the gateways.
+   *
+   * @var string[]
+   */
+  protected $names;
+
+  /**
+   * The default gateway.
+   *
+   * @var string
+   */
+  protected $defaultGateway;
+
+  /**
+   * Create new GatewayManager instance.
+   *
+   * @param \Traversable $namespaces
+   *   The namespaces to search for the gateway plugins.
+   * @param \Drupal\Core\Config\ConfigFactory
+   *   Handles the instantiation of gateways based on stored configuration.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   Cache backend instance to use.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface
+   *   Module handler for calling module hooks.
+   */
+  public function __construct(\Traversable $namespaces, ConfigFactory $config, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
+    parent::__construct('Plugin/Gateway', $namespaces, $module_handler, 'Drupal\sms\Gateway\GatewayInterface', 'Drupal\sms\Annotation\SmsGateway');
+    $this->setCacheBackend($cache_backend, 'sms_gateways');
+    $this->alterInfo('sms_gateway_info');
+    $this->config = $config;
+    $this->defaultGateway = $this->config->get('sms.settings')->get('default_gateway');
+  }
+
+  public function getGatewayPlugins() {
+    return $this->discovery->getDefinitions();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getGateway($gateway_id) {
+    // Ensure gateway list has been built.
+    $gateways = $this->getAvailableGateways();
+    if (!isset($gateways[$gateway_id])) {
+      return null;
+    }
+    else {
+      return $gateways[$gateway_id];
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDefaultGateway() {
+    if (!isset($this->defaultGateway)) {
+      $this->defaultGateway = $this->config->get('sms.settings')
+        ->get('default_gateway');
+    }
+    return $this->getGateway($this->defaultGateway);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setDefaultGateway($gateway_id) {
+    // Cannot make a disabled gateway default.
+    if ($this->getGateway($gateway_id)->isEnabled()) {
+      $this->defaultGateway = $gateway_id;
+      $this->config->getEditable('sms.settings')
+        ->set('default_gateway', $this->defaultGateway)
+        ->save();
+    }
+    else {
+      throw new \LogicException('A disabled gateway cannot be made the default.');
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAvailableGateways() {
+    if (!isset($this->gateways)) {
+      $this->buildGateways();
+    }
+    return $this->gateways;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getEnabledGateways() {
+    return array_filter($this->getAvailableGateways(), function(GatewayInterface $gateway) {
+      $gateway->isEnabled();
+    });
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setEnabledGateways(array $gateway_ids) {
+    foreach ($gateway_ids as $gateway_id) {
+      $gateway = $this->getGateway($gateway_id);
+      $gateway->setEnabled(TRUE);
+      $this->saveGateway($gateway);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isGatewayEnabled($gateway_id) {
+    return $this->getGateway($gateway_id)->isEnabled();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function saveGateway(GatewayInterface $gateway) {
+    $this->config->getEditable('sms.gateway.config')
+      ->set($gateway->getName(), $gateway->getConfiguration())
+      ->save();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addGateway($plugin_id, array $configuration) {
+    $name = $configuration['name'];
+    if (!isset($configuration['name'])) {
+      throw new \InvalidArgumentException('A machine name is required to create an SMS gateway.');
+    }
+    $configuration['plugin_id'] = $plugin_id;
+    $this->gateways[$name] = $gateway = $this->createInstance($plugin_id, $configuration);
+    $this->names[$name] = $this->gateways[$name]->getLabel();
+    $this->saveGateway($gateway);
+  }
+
+  /**
+   * Builds the gateway plugin objects from the definitions.
+   *
+   * @return array
+   *   \Drupal\sms\Gateway\GatewayInterface[]
+   *
+   * @todo remove BC-shim when all gateways are converted to plugins.
+   */
+  protected function buildGateways() {
+    // Non-configurable gateways do not need configuration and so should be
+    // available by default.
+    // Get non-configurable plugins and set them up if not already done.
+    $definitions = $this->getDefinitions();
+    $plugin_config = [];
+    foreach ($definitions as $plugin_id => $definition) {
+      if (!$definition['configurable']) {
+        $plugin_config[$plugin_id] = ['plugin_id' => $plugin_id];
+      }
+    }
+
+    // Get configured gateways.
+    $instance_configs = $this->config->get('sms.gateway.config')->getRawData();
+    $instance_configs += $plugin_config;
+    foreach ($instance_configs as $id => $instance_config) {
+      // Note that DefaultFactory::createInstance will get the right definitions.
+      if (!isset($instance_config['plugin_id'])) {
+        // @todo Throw exception here???
+//        throw new \InvalidArgumentException(sprintf('Gateway %s configured without a plugin id.', $id));
+        continue;
+      }
+      $this->gateways[$id] = $this->createInstance($instance_config['plugin_id'], $instance_config);
+      $this->names[$id] = $this->gateways[$id]->getLabel();
+    }
+
+
+    // BC-shim for gateways still based on hook_gateway_info implementation.
+    $gateway_array = $this->moduleHandler->invokeAll('gateway_info');
+    $gateway_configs = $this->config->get('sms.gateway.config');
+    foreach ($gateway_array as $id => $info) {
+      $settings = $gateway_configs->get($id);
+      if (!isset($settings)) $settings = array();
+      $info['identifier'] = $id;
+      $this->gateways[$id] = new HookGateway($info, $settings);
+      $this->names[$id] = $this->gateways[$id]->getLabel();
+    }
+  }
+
+}
diff --git a/src/Gateway/GatewayManagerInterface.php b/src/Gateway/GatewayManagerInterface.php
new file mode 100644
index 0000000..a127611
--- /dev/null
+++ b/src/Gateway/GatewayManagerInterface.php
@@ -0,0 +1,100 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\sms\GatewayInterface
+ */
+namespace Drupal\sms\Gateway;
+
+/**
+ * Manages SMS Gateways.
+ */
+interface GatewayManagerInterface {
+
+  /**
+   * Get the list of all discoverable SMS gateways.
+   *
+   * @return \Drupal\sms\Gateway\GatewayInterface[]
+   */
+  public function getAvailableGateways();
+
+  /**
+   * Get the list of all enabled SMS gateways.
+   *
+   * @return \Drupal\sms\Gateway\GatewayInterface[]
+   */
+  public function getEnabledGateways();
+
+  /**
+   * Get the default SMS gateway.
+   *
+   * @return \Drupal\sms\Gateway\GatewayInterface
+   */
+  public function getDefaultGateway();
+
+  /**
+   * Get the gateway whose id is specified
+   *
+   * @param string $gateway_id
+   *   The gateway id.
+   * @return \Drupal\sms\Gateway\GatewayInterface
+   */
+  public function getGateway($gateway_id);
+
+  /**
+   * Sets the list of gateways that are enabled.
+   *
+   * @param array $gateway_ids
+   *   An array of ids of the gateways to be enabled.
+   */
+  public function setEnabledGateways(array $gateway_ids);
+
+  /**
+   * Sets the default sms gateway for messaging.
+   *
+   * @param string $gateway_id
+   *   The gateway id.
+   */
+  public function setDefaultGateway($gateway_id);
+
+  /**
+   * Gets the status of the specified gateway.
+   *
+   * @param string $gateway_id
+   *   The id of the gateway to be checked.
+   * @return bool
+   *   TRUE if the gateway is enabled and FALSE otherwise.
+   */
+  public function isGatewayEnabled($gateway_id);
+
+  /**
+   * Creates a new gateway of a type from specified configuration.
+   *
+   * @param string $plugin_id
+   *   The plugin ID of an existing gateway plugin.
+   * @param array $configuration
+   *   The configuration to be saved for the gateway. At a minimum must contain:
+   *   - name: The machine name of the gateway
+   *   - label: The human-readable name of the gateway
+   *
+   * @return \Drupal\sms\Gateway\GatewayInterface
+   *   A newly created gateway instance.
+   */
+  public function addGateway($plugin_id, array $configuration);
+
+  /**
+   * Saves the configuration of a gateway.
+   *
+   * @param \Drupal\sms\Gateway\GatewayInterface
+   *   The gateway object whose configuration is to be saved.
+   */
+  public function saveGateway(GatewayInterface $gateway);
+
+  /**
+   * Gets the list of gateway plugins discovered by this gateway manager.
+   *
+   * @return \Drupal\sms\Gateway\GatewayInterface[]
+   */
+  public function getGatewayPlugins();
+
+}
diff --git a/src/Gateway/HookGateway.php b/src/Gateway/HookGateway.php
new file mode 100644
index 0000000..a57092c
--- /dev/null
+++ b/src/Gateway/HookGateway.php
@@ -0,0 +1,153 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\sms\Gateway\HookGateway
+ */
+
+namespace Drupal\sms\Gateway;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\sms\Message\SmsMessageInterface;
+use Drupal\sms\Message\SmsMessageResult;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * BC-shim class to allow hook_gateway_info() based gateways to still work.
+ */
+class HookGateway extends GatewayBase {
+
+  /**
+   * Constructs a new hook-based gateway.
+   *
+   * @param array $info
+   *   The info extracted from hook_gateway_info() for this gateway.
+   * @param array $configuration
+   *   The settings stored in config for this gateway.
+   */
+  public function __construct(array $info, array $configuration) {
+    $this->configuration = $configuration + ['custom' => []];
+    $this->pluginId = 'sms_hook_gateway';
+    $this->pluginDefinition = $info;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function send(SmsMessageInterface $sms, array $options) {
+    if (is_callable($this->pluginDefinition['send'])) {
+      $result = $this->pluginDefinition['send']($sms, $options);
+      return new SmsMessageResult($result);
+    }
+    throw new \BadMethodCallException(sprintf('No send method defined for gateway %s', $this->configuration['name']));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function balance() {
+    return 0;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deliveryReport(Request $request) {
+    return;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getError() {
+    return array();
+  }
+
+  /**
+   * Gets the machine  name of the gateway.
+   *
+   * @return string
+   */
+  public function getIdentifier() {
+    return $this->pluginDefinition['identifier'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getName() {
+    return $this->pluginDefinition['identifier'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLabel() {
+    return $this->pluginDefinition['name'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isConfigurable() {
+    return isset($this->pluginDefinition['configure form']) && is_callable($this->pluginDefinition['configure form']);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+    if (is_callable($this->pluginDefinition['configure form'])) {
+      return array_merge($form, $this->pluginDefinition['configure form']($this->getCustomConfiguration()));
+    }
+    else {
+      throw new \RuntimeException($this->t('Configuration form callback not available.'));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
+    $function = $this->pluginDefinition['configure form'] . '_validate';
+    if (is_callable($function)) $function($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
+    $function = $this->pluginDefinition['configure form'] . '_submit';
+    if (is_callable($function)) {
+      // Call the hook gateway submit callback.
+      $function($form, $form_state);
+    }
+    // Update the configuration.
+    $this->setCustomConfiguration($form_state->getValues());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function sendForm(array &$form, FormStateInterface $form_state) {
+    if (is_callable($this->pluginDefinition['send form'])) {
+      return $this->pluginDefinition['send form']($form, $form_state);
+    }
+    else {
+      return array();
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateNumbers(array $numbers, array $options = array()) {
+    if (is_callable($this->pluginDefinition['validate number'])) {
+      return $this->pluginDefinition['validate number']($numbers, $options);
+    }
+    else {
+      return array();
+    }
+  }
+
+}
diff --git a/src/Message/SmsMessage.php b/src/Message/SmsMessage.php
index 48e3d60..4107247 100644
--- a/src/Message/SmsMessage.php
+++ b/src/Message/SmsMessage.php
@@ -45,7 +45,7 @@ class SmsMessage implements SmsMessageInterface {
   protected $options = array();
 
   /**
-   * Create a new instance of an SMS message.
+   * Creates a new instance of an SMS message.
    *
    * @param string
    *   The sender of the message.
diff --git a/src/Message/SmsMessageInterface.php b/src/Message/SmsMessageInterface.php
index de73cff..a6a2299 100644
--- a/src/Message/SmsMessageInterface.php
+++ b/src/Message/SmsMessageInterface.php
@@ -66,21 +66,21 @@ interface SmsMessageInterface {
   const STATUS_EXPIRED = 408;
 
   /**
-   * Get the list of recipients of this SMS message.
+   * Gets the list of recipients of this SMS message.
    *
    * @return array
    */
   public function getRecipients();
 
   /**
-   * Get the options for building or sending this SMS message.
+   * Gets the options for building or sending this SMS message.
    *
    * @return array
    */
   public function getOptions();
 
   /**
-   * Get the option specified by the key $name.
+   * Gets the option specified by the key $name.
    *
    * @param string
    *   The name of the option.
@@ -89,14 +89,14 @@ interface SmsMessageInterface {
   public function getOption($name);
 
   /**
-   * Get the name of the sender of this SMS message.
+   * Gets the name of the sender of this SMS message.
    *
    * @return string
    */
   public function getSender();
 
   /**
-   * Get the message to be sent.
+   * Gets the message to be sent.
    *
    * @return string
    */
diff --git a/src/Message/SmsMessageResultInterface.php b/src/Message/SmsMessageResultInterface.php
index be2f0be..1f89191 100644
--- a/src/Message/SmsMessageResultInterface.php
+++ b/src/Message/SmsMessageResultInterface.php
@@ -48,7 +48,7 @@ interface SmsMessageResultInterface {
   /**
    * Gets the credit balance after the SMS was sent.
    *
-   * @return integer
+   * @return int
    *   The value of the balance. This number is in the SMS gateway's chosen
    *   denomination.
    */
@@ -57,7 +57,7 @@ interface SmsMessageResultInterface {
   /**
    * Gets the SMS credits used for this transaction.
    *
-   * @return integer
+   * @return int
    *   The amount of SMS credits used in the gateway's chosen denomination.
    */
   public function getCreditsUsed();
diff --git a/src/Plugin/Gateway/LogGateway.php b/src/Plugin/Gateway/LogGateway.php
new file mode 100644
index 0000000..4e5c08d
--- /dev/null
+++ b/src/Plugin/Gateway/LogGateway.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\sms\Plugin\Gateway\LogGateway
+ */
+
+namespace Drupal\sms\Plugin\Gateway;
+
+use Drupal\sms\Gateway\GatewayBase;
+use Drupal\sms\Message\SmsMessageInterface;
+use Drupal\sms\Message\SmsMessageResult;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * @SmsGateway(
+ *   id = "log",
+ *   label = @Translation("Log only"),
+ *   configurable = FALSE,
+ * )
+ */
+class LogGateway extends GatewayBase {
+
+  /**
+   * {@inheritdoc}
+   *
+   * Log sms message to drupal watchdog().
+   */
+  public function send(SmsMessageInterface $sms, array $options) {
+    $this->logger()->notice('SMS message sent to %number with the text: @message',
+      ['%number' => implode(', ', $sms->getRecipients()), '@message' => $sms->getMessage()]);
+    return new SmsMessageResult(['status' => TRUE]);
+  }
+
+
+  /**
+   * {@inheritdoc}
+   */
+  public function balance() {
+    return 0;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deliveryReport(Request $request) {
+    return;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getError() {
+    return array();
+  }
+
+}
diff --git a/src/Provider/DefaultSmsProvider.php b/src/Provider/DefaultSmsProvider.php
index 6437999..d6057fb 100644
--- a/src/Provider/DefaultSmsProvider.php
+++ b/src/Provider/DefaultSmsProvider.php
@@ -8,7 +8,10 @@
 namespace Drupal\sms\Provider;
 
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\sms\Gateway\GatewayInterface;
+use Drupal\sms\Gateway\GatewayManagerInterface;
 use Drupal\sms\Message\SmsMessageInterface;
+use Drupal\sms\Message\SmsMessageResultInterface;
 use Drupal\sms\SmsException;
 
 /**
@@ -17,19 +20,29 @@ use Drupal\sms\SmsException;
 class DefaultSmsProvider implements SmsProviderInterface {
 
   /**
-   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   * The SMS gateway manager.
    *
+   * @var \Drupal\sms\Gateway\GatewayManager
+   */
+  protected $gatewayManager;
+
+  /**
    * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
    */
   protected $moduleHandler;
 
   /**
    * Creates a new instance of the default SMS provider.
    *
+   * @param \Drupal\sms\Gateway\GatewayManagerInterface
+   *   The gateway manager.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface
    *   The module handler.
    */
-  public function __construct(ModuleHandlerInterface $module_handler) {
+  public function __construct(GatewayManagerInterface $gateway_manager, ModuleHandlerInterface $module_handler) {
+    $this->gatewayManager = $gateway_manager;
     $this->moduleHandler = $module_handler;
   }
 
@@ -42,12 +55,11 @@ class DefaultSmsProvider implements SmsProviderInterface {
   public function send(SmsMessageInterface $sms, array $options = array()) {
     // Check if a preferred gateway is specified in the $options.
     if (isset($options['gateway'])) {
-      $gateway_id = $options['gateway'];
+      $gateway = $this->gatewayManager->getGateway($options['gateway']);
     }
-    if (empty($gateway_id)) {
-      $gateway_id = sms_default_gateway_id();
+    if (empty($gateway)) {
+      $gateway = $this->gatewayManager->getDefaultGateway();
     }
-    $gateway = sms_gateways('gateway', $gateway_id);
 
     if ($this->preProcess($sms, $options, $gateway)) {
       $result = $this->process($sms, $options, $gateway);
@@ -66,16 +78,16 @@ class DefaultSmsProvider implements SmsProviderInterface {
    *   The SMS to be sent.
    * @param array $options
    *   The gateway options.
-   * @param array $gateway
+   * @param \Drupal\sms\Gateway\GatewayInterface $gateway
    *   The default gateway for sending this message.
    *
    * @return bool
    *   TRUE if the message was successfully sent.
    */
-  protected function process(SmsMessageInterface $sms, array $options, array $gateway) {
+  protected function process(SmsMessageInterface $sms, array $options, GatewayInterface $gateway) {
     $this->moduleHandler->invokeAll('sms_send', [$sms, $options, $gateway]);
     // @todo Apply token replacements.
-    $response = $gateway['send']($sms, $options);
+    $response = $gateway->send($sms, $options);
     $result = $this->handleResult($response, $sms);
     return $result;
   }
@@ -87,17 +99,17 @@ class DefaultSmsProvider implements SmsProviderInterface {
    *   The SMS to be sent.
    * @param array $options
    *   Additional options to be passed to the SMS gateway.
-   * @param array $gateway
-       The default gateway for sending this message.
+   * @param \Drupal\sms\Gateway\GatewayInterface $gateway
+   *   The default gateway for sending this message.
    *
-   * @returns bool
+   * @return bool
    *   Whether to continue sending or not.
    */
-  protected function preProcess(SmsMessageInterface $sms, array $options, array $gateway) {
+  protected function preProcess(SmsMessageInterface $sms, array $options, GatewayInterface $gateway) {
     // Call the send pre process hooks.
     $return = $this->moduleHandler->invokeAll('sms_send_process', ['pre process', $sms, $options, $gateway, NULL]);
     // Return FALSE if any of the hooks returned FALSE.
-    return !in_array(FALSE, $return);
+    return !in_array(FALSE, $return, TRUE);
   }
 
   /**
@@ -107,12 +119,12 @@ class DefaultSmsProvider implements SmsProviderInterface {
    *   The SMS that was sent.
    * @param array $options
    *   Additional options that were passed to the SMS gateway.
-   * @param array $gateway
+   * @param \Drupal\sms\Gateway\GatewayInterface $gateway
    *   The default gateway for sending this message.
-   * @param bool $result
-   *   Whether the SMS sending was successful or not.
+   * @param array $result
+   *   The result returned from SMS sending.
    */
-  protected function postProcess(SmsMessageInterface $sms, array $options, array $gateway, $result) {
+  protected function postProcess(SmsMessageInterface $sms, array $options, GatewayInterface $gateway, $result) {
     // Call the send post process hooks.
     $this->moduleHandler->invokeAll('sms_send_process', ['post process', $sms, $options, $gateway, $result]);
   }
@@ -120,8 +132,8 @@ class DefaultSmsProvider implements SmsProviderInterface {
   /**
    * Handles the response back from the SMS gateway.
    *
-   * @param array $result
-   *   The result to be handled.
+   * @param \Drupal\sms\Message\SmsMessageResultInterface $result
+   *   The message result to be handled.
    * @param \Drupal\sms\Message\SmsMessageInterface $sms
    *   The message that was sent.
    *
@@ -131,14 +143,14 @@ class DefaultSmsProvider implements SmsProviderInterface {
    *
    * @throws \Drupal\sms\SmsException
    */
-  protected function handleResult(array $result, SmsMessageInterface $sms) {
-    if ($result['status']) {
+  protected function handleResult(SmsMessageResultInterface $result, SmsMessageInterface $sms) {
+    if ($result->getStatus()) {
       return TRUE;
     }
     else {
       // @todo Review all of this.
       $error_message = t('Sending SMS to %number failed.', ['%number' => implode(',', $sms->getRecipients())]);
-      if ($message = $result['error_message']) {
+      if ($message = $result->getErrorMessage()) {
         $error_message .= t(' The gateway said %message.', ['%message' => $message]);
       }
       \Drupal::logger('sms')->error($message);
@@ -150,7 +162,7 @@ class DefaultSmsProvider implements SmsProviderInterface {
    * {@inheritdoc}
    */
   public function incoming(SmsMessageInterface $sms, array $options) {
-    // @todo Implement rules event integration here for incoming sms.
+    // @todo Implement rules event integration here for incoming SMS.
     // Execute three phases.
     $this->moduleHandler->invokeAll('sms_incoming', array('pre process', $sms, $options));
     $this->moduleHandler->invokeAll('sms_incoming', array('process', $sms, $options));
@@ -160,8 +172,8 @@ class DefaultSmsProvider implements SmsProviderInterface {
   /**
    * {@inheritdoc}
    */
-  public function receipt($number, $reference, $message_status = SMS_GW_UNKNOWN_STATUS, $options = array()) {
-    // @todo Implement rules event integration here for incoming sms.
+  public function receipt($number, $reference, $message_status = GatewayInterface::STATUS_UNKNOWN, array $options = array()) {
+    // @todo Implement rules event integration here for incoming SMS.
     // Execute three phases.
     $this->moduleHandler->invokeAll('sms_receipt', array('pre process', $number, $reference, $message_status, $options));
     $this->moduleHandler->invokeAll('sms_receipt', array('process', $number, $reference, $message_status, $options));
diff --git a/src/Provider/SmsProviderInterface.php b/src/Provider/SmsProviderInterface.php
index 6ad0ad8..bcaf66d 100644
--- a/src/Provider/SmsProviderInterface.php
+++ b/src/Provider/SmsProviderInterface.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\sms\Provider;
 
+use Drupal\sms\Gateway\GatewayInterface;
 use Drupal\sms\Message\SmsMessageInterface;
 
 /**
@@ -47,7 +48,8 @@ interface SmsProviderInterface {
    * messages in a standard format for processing, and provides a basic set of
    * status codes for common code handling.
    *
-   * Allowed message status codes are defined as constants in sms.module
+   * Allowed message status codes are defined as constants in
+   * @link \Drupal\sms\Gateway\GatewayInterface @endlink
    *
    * The original gateway code and string will often be provided in the $options
    * array as 'gateway_message_status' and 'gateway_message_status_text'.
@@ -63,6 +65,6 @@ interface SmsProviderInterface {
    * @param array $options
    *   (optional) Extended options passed by the receipt receiver.
    */
-  public function receipt($number, $reference, $message_status = SMS_GW_UNKNOWN_STATUS, $options = array());
+  public function receipt($number, $reference, $message_status = GatewayInterface::STATUS_UNKNOWN, array $options = array());
 
 }
diff --git a/src/Tests/SmsFrameworkWebTest.php b/src/Tests/SmsFrameworkWebTest.php
index 9f5e772..4092b15 100644
--- a/src/Tests/SmsFrameworkWebTest.php
+++ b/src/Tests/SmsFrameworkWebTest.php
@@ -22,7 +22,11 @@ class SmsFrameworkWebTest extends WebTestBase {
    * Tests that the correct gateways list is obtained.
    */
   public function testGatewaysList() {
-    $this->assertEqual(array('log' => t('Log only'), 'test' => t('For testing')), sms_gateways('names'));
+    $test_gateways = [
+      'log' => 'Log only',
+      'test' => 'For testing',
+    ];
+    $this->assertEqual($test_gateways, sms_gateways('names'));
   }
 
   /**
@@ -31,20 +35,21 @@ class SmsFrameworkWebTest extends WebTestBase {
   public function testDefaultGateway() {
     // Test initial default gateway.
     $gw = sms_default_gateway();
-    $this->assertEqual($gw['identifier'], 'log', 'Initial default gateway is "log".');
+    $this->assertEqual($gw->getIdentifier(), 'log', 'Initial default gateway is "log".');
 
     $this->drupalLogin($this->drupalCreateUser(['administer smsframework']));
     // Set up default log gateway.
-    $this->drupalPostForm('admin/config/smsframework/gateways', ['default' => 'log'], t('Set default gateway'));
+    $this->drupalPostForm('admin/config/smsframework/gateways', ['default' => 'log'], 'Save settings');
     $this->assertResponse(200);
     $gw = sms_default_gateway();
-    $this->assertEqual($gw['identifier'], 'log', 'Default gateway set to log.');
+    $this->assertEqual($gw->getIdentifier(), 'log', 'Default gateway set to log.');
 
     // Set up default test gateway.
-    $this->drupalPostForm('admin/config/smsframework/gateways', ['default' => 'test'], t('Set default gateway'));
+    $this->drupalPostForm('admin/config/smsframework/gateways', ['default' => 'test'], 'Save settings');
     $this->assertResponse(200);
+    $this->rebuildContainer();
     $gw = sms_default_gateway();
-    $this->assertEqual($gw['identifier'], 'test', 'Default gateway set to test.');
+    $this->assertEqual($gw->getIdentifier(), 'test', 'Default gateway set to test.');
   }
 
   /**
@@ -59,10 +64,11 @@ class SmsFrameworkWebTest extends WebTestBase {
       'method' => 0,
       'ssl' => false,
     );
-    $this->drupalPostForm('admin/config/smsframework/gateways/test', $edit, t('Save'));
+    $this->drupalPostForm('admin/config/smsframework/gateways/test', $edit, 'Save configuration');
     $this->assertResponse(200);
+    $this->rebuildContainer();
     $gateway = sms_gateways('gateway', 'test');
-    $this->assertEqual($edit, $gateway['configuration'], 'SMS Test gateway successfully configured.');
+    $this->assertEqual($edit, $gateway->getCustomConfiguration(), 'SMS Test gateway successfully configured.');
   }
 
   /**
diff --git a/tests/modules/sms_test_gateway/sms_test_gateway.module b/tests/modules/sms_test_gateway/sms_test_gateway.module
index 68d1b4f..67c224e 100644
--- a/tests/modules/sms_test_gateway/sms_test_gateway.module
+++ b/tests/modules/sms_test_gateway/sms_test_gateway.module
@@ -5,6 +5,7 @@
  * A test gateway to be used for testing the sms framework
  */
 use Drupal\sms\Message\SmsMessageInterface;
+use Drupal\sms\Message\SmsMessageResult;
 
 /**
  * Implements hook_gateway_info().
@@ -42,13 +43,13 @@ function sms_test_gateway_send(SmsMessageInterface $sms, array $options = array(
     $result['options'] = $options;
     \Drupal::state()->set('sms_test_gateway_result', $result);
   }
-  return [
+  return new SmsMessageResult([
     'status' => TRUE,
     'credit_balance' => 1000,
     'credits_used' => count($sms->getRecipients()),
     'error_message' => '',
     'report' => $result,
-  ];
+  ]);
 }
 
 function sms_test_gateway_result($reset = FALSE) {
@@ -158,23 +159,25 @@ function sms_test_gateway_config_form($configuration) {
  * less than 10 digits, or has non-numeric characters or has country code 990,
  * 997 or 999.
  *
- * @param string $number
- *   The number to be validated.
+ * @param array $numbers
+ *   The numbers to be validated.
  *
  * @return array
  *   List of errors.
  */
-function sms_test_gateway_validate_number($number) {
-  $code = substr($number, 0, 3);
+function sms_test_gateway_validate_number($numbers) {
   $errors = array();
-  if (preg_match('/[^0-9]/', $number)) {
-    $errors[] = t('Non-numeric character found in number.');
-  }
-  if (strlen($number) > 15 || strlen($number) < 10) {
-    $errors[] = t('Number longer than 15 digits or shorter than 10 digits.');
-  }
-  if ($code == '990' || $code == '997' || $code == '999') {
-    $errors[] = t('Country code not allowed');
+  foreach ($numbers as $number) {
+    $code = substr($number, 0, 3);
+    if (preg_match('/[^0-9]/', $number)) {
+      $errors[] = t('Non-numeric character found in number.');
+    }
+    if (strlen($number) > 15 || strlen($number) < 10) {
+      $errors[] = t('Number longer than 15 digits or shorter than 10 digits.');
+    }
+    if ($code == '990' || $code == '997' || $code == '999') {
+      $errors[] = t('Country code not allowed');
+    }
   }
   return $errors;
 }
