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/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 650f07e..4db981b 100644
--- a/sms.module
+++ b/sms.module
@@ -14,37 +14,14 @@ define('SMS_DIR_OUT',   1);
 define('SMS_DIR_IN',    2);
 define('SMS_DIR_ALL',   4);
 
-// Message status codes.
-// 0=Unknown, 2xx=Positive, 3xx=Positive/Neutral (context-dependent), 4xx=Negative
-define('SMS_MSG_STATUS_UNKNOWN',      0);
-define('SMS_MSG_STATUS_OK',         200);
-define('SMS_MSG_STATUS_DELIVERED',  202);
-define('SMS_MSG_STATUS_QUEUED',     302);
-define('SMS_MSG_STATUS_ERROR',      400);
-define('SMS_MSG_STATUS_NOCREDIT',   402);
-define('SMS_MSG_STATUS_EXPIRED',    408);
-
-// 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);
-
-// Carrier status.
+//
 define('SMS_CARRIER_DEFAULT', 0);
 define('SMS_CARRIER_OVERRIDDEN', 1);
 define('SMS_CARRIER_NORMAL', 3);
 
+use Drupal\sms\Gateway\GatewayInterface;
+use Drupal\sms\Message\SmsMessage;
+
 /**
  * Implements hook_theme().
  */
@@ -83,72 +60,8 @@ function sms_cron_queue_info() {
  * @see sms_handle_result().
  */
 function sms_send($number, $message, $options = array()) {
-  // Check if preferred gateway is specified in the $options.
-  if (isset($options['gateway'])) {
-    $gateway = sms_gateways('gateway', $options['gateway']);
-  }
-  if (empty($gateway)) {
-    $gateway = sms_default_gateway();
-  }
-
-  foreach (\Drupal::moduleHandler()->getImplementations('sms_send') as $module) {
-    $function = $module . '_sms_send';
-    $function($number, $message, $options, $gateway);
-  }
-
-
-  if (\Drupal::moduleHandler()->moduleExists('token')) {
-    $message = \Drupal::token()->replace($message);
-  }
-
-  $response = NULL;
-  if (isset($gateway['send']) && function_exists($gateway['send'])) {
-    $response = $gateway['send']($number, $message, $options);
-  }
-  $result = sms_handle_result($response, $number, $message);
-
-  // Post process hook.
-  foreach (\Drupal::moduleHandler()->getImplementations('sms_send_process') as $module) {
-    $function = $module . '_sms_send_process';
-    $function('post process', $number, $message, $options, $gateway, $result);
-  }
-
-  return $result;
-}
-
-/**
- * Handles the response back from the sms gateway.
- *
- * This method also logs an error message to watchdog if there was a failure.
- *
- * @param array $result
- *   An array containing information on the message response, with the keys:
- *   - status: true if it was successful, false otherwise.
- *   - message: an error message if the 'status' is false.
- * @param string $number
- *   The comma-separated list of the message's recipient numbers.
- * @param string $message
- *   The message that was sent.
- *
- * @return bool
- *   true if the message was sent to the server, false otherwise.
- */
-function sms_handle_result($result, $number, $message) {
-  if ($result['status']) {
-    return TRUE;
-  }
-  else {
-    $error_message = 'Sending SMS to %number failed.';
-    $variables['%number'] = $number;
-    if ($result['message']) {
-      $error_message .= ' The gateway said ' . $result['message'];
-      if (!empty($result['variables'])) {
-        $variables = array_merge($variables, $result['variables']);
-      }
-    }
-    \Drupal::logger('sms')->error($error_message, $variables);
-    return FALSE;
-  }
+  $sms = new SmsMessage($options['sender'], explode(',', $number), $message, $options);
+  return \Drupal::service('sms_provider.default')->send($sms, $options);
 }
 
 /**
@@ -180,162 +93,80 @@ function sms_incoming_queue_worker(array $item) {
  *   An array of additional options.
  */
 function sms_incoming($number, $message, $options = array()) {
-  if (\Drupal::moduleHandler()->moduleExists('rules')) {
-    $options += array('number' => $number, 'message' => $message);
-    rules_invoke_event('sms_incoming', $options);
-  }
-
-  // Execute three phases
-  \Drupal::moduleHandler()->invokeAll('sms_incoming', ['pre process', $number, $message, $options]);
-  \Drupal::moduleHandler()->invokeAll('sms_incoming', ['process', $number, $message, $options]);
-  \Drupal::moduleHandler()->invokeAll('sms_incoming', ['post process', $number, $message, $options]);
+  $sms = new SmsMessage($options['sender'], explode(',', $number), $message, $options);
+  \Drupal::service('sms_provider.default')->incoming($sms, $options);
 }
 
 /**
  * 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
  */
-function sms_receipt($number, $reference, $message_status = SMS_GW_UNKNOWN_STATUS, $options = array()) {
-  // Execute three phases
-  \Drupal::moduleHandler()->invokeAll('sms_receipt', 'pre process', $number, $reference, $message_status, $options);
-  \Drupal::moduleHandler()->invokeAll('sms_receipt', 'process', $number, $reference, $message_status, $options);
-  \Drupal::moduleHandler()->invokeAll('sms_receipt', 'post process', $number, $reference, $message_status, $options);
+function sms_receipt($number, $reference, $message_status = GatewayInterface::STATUS_UNKNOWN, $options = array()) {
+  \Drupal::service('sms_provider.default')->incoming($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 string $number
- *   Comma-separated list of mobile numbers message was sent to.
- * @param string $message
- *   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($number, $message, $options) {
-  \Drupal::logger('sms')->info('SMS message sent to %number with the text: @message', array('%number' => $number, '@message' => $message));
-  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;
-  }
-}
-
-/**
- * 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'];
+      return $gateway_manager->getAvailableGateways();
   }
-
-  asort($_names);
-
-  return array($_gateways, $_names);
 }
 
 /**
@@ -362,10 +193,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 = array();
+  $form['gateway']['#tree'] = TRUE;
+  $form['gateway'] = array_merge($gateway->sendForm($form, $form_state), $form['gateway']);
 
   return $form;
 }
@@ -629,10 +459,8 @@ function sms_validate_number(&$number, $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
new file mode 100644
index 0000000..39f9257
--- /dev/null
+++ b/sms.services.yml
@@ -0,0 +1,8 @@
+services:
+  sms_provider.default:
+    class: Drupal\sms\Provider\DefaultSmsProvider
+    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
new file mode 100644
index 0000000..0420c3c
--- /dev/null
+++ b/src/DeliveryReportController.php
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\sms\DeliveryReportController
+ */
+
+namespace Drupal\sms;
+
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\sms\Gateway\GatewayManagerInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Provides delivery reports acknowledgement and passes to the correct gateway.
+ */
+class DeliveryReportController implements ContainerInjectionInterface {
+
+  /**
+   * The gateway manager.
+   *
+   * @var \Drupal\sms\Gateway\GatewayManagerInterface
+   */
+  protected $gatewayManager;
+
+  /**
+   * The request stack.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
+  /**
+   * Creates an new delivery report controller.
+   *
+   * @param \Drupal\sms\Gateway\GatewayManagerInterface $gateway_manager
+   *   The gateway manager.
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The request stack.
+   */
+  public function __construct(GatewayManagerInterface $gateway_manager, RequestStack $request_stack) {
+    $this->gatewayManager = $gateway_manager;
+    $this->requestStack = $request_stack;
+  }
+
+  /**
+   * Acknowledges delivery reports and passes them to the correct gateway.
+   *
+   * @param string $gateway_id
+   *   The gateway id of the gateway that is to handle the delivery report.
+   *
+   * @return \Symfony\Component\HttpFoundation\Response
+   */
+  public function acknowledgeDelivery($gateway_id) {
+    $acknowledgement = $this->gatewayManager->getGateway($gateway_id)->deliveryReport($this->requestStack->getCurrentRequest());
+    return new Response($acknowledgement);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  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..ff97ddb 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,29 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  * form more streamlined
  */
 class GatewayConfigForm extends ConfigFormBase {
+
+  /**
+   * The gateway manager.
+   *
+   * @var \Drupal\sms\Gateway\GatewayManagerInterface
+   */
+  protected $gatewayManager;
+
+  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 +55,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 +90,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 +116,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..7b77bd6
--- /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 $gateways) {
+    foreach ($gateways 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..5c173ec
--- /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 $gateways
+   *   An array of ids of the gateways to be enabled.
+   */
+  public function setEnabledGateways(array $gateways);
+
+  /**
+   * 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
new file mode 100644
index 0000000..55bcb78
--- /dev/null
+++ b/src/Message/SmsMessage.php
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\sms\Message\SmsMessage.
+ */
+
+namespace Drupal\sms\Message;
+
+/**
+ * Basic implementation of an sms message.
+ */
+class SmsMessage implements SmsMessageInterface {
+
+  /**
+   * The unique identifier for this message.
+   *
+   * @var string
+   */
+  protected $uuid;
+
+  /**
+   * The sender of the message.
+   *
+   * @var string
+   */
+  protected $sender;
+
+  /**
+   * @var array
+   *   The recipients of the message.
+   */
+  protected $recipients = array();
+
+  /**
+   * @var string
+   *   The content of the message to be sent.
+   */
+  protected $message;
+
+  /**
+   * @var string
+   *   Other options to be used for the sms.
+   */
+  protected $options = array();
+
+  /**
+   * Create a new instance of an sms message.
+   *
+   * @param string
+   *   The sender of the message.
+   * @param array
+   *   The list of recipient phone numbers for the message.
+   * @param string
+   *   The actual sms message to be sent.
+   * @param array
+   *   Additional options to be considered in building the sms message
+   */
+  public function __construct($sender, array $recipients, $message, array $options) {
+    $this->sender = $sender;
+    $this->recipients = $recipients;
+    $this->message = $message;
+    $this->options = $options;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSender() {
+    return $this->sender;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMessage() {
+    return $this->message;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRecipients() {
+    return $this->recipients;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOptions() {
+    return $this->options;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOption($name) {
+    if (array_key_exists($name, $this->options)) {
+      return $this->options[$name];
+    }
+    return NULL;
+  }
+
+}
diff --git a/src/Message/SmsMessageInterface.php b/src/Message/SmsMessageInterface.php
new file mode 100644
index 0000000..7eccf3c
--- /dev/null
+++ b/src/Message/SmsMessageInterface.php
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * @file
+ * Contains definition of \Drupal\sms\Message\SmsMessageInterface
+ */
+
+namespace Drupal\sms\Message;
+
+/**
+ * Contains information about an sms.
+ */
+interface SmsMessageInterface {
+
+  // Message status codes
+  // 0=Unknown, 2xx=Positive, 3xx=Positive/Neutral (context-dependent), 4xx=Negative
+
+  /**
+   * Status Unknown.
+   *
+   * A message would have this to indicate unknown status.
+   */
+  const STATUS_UNKNOWN = 0;
+
+  /**
+   * Status OK.
+   *
+   * A message with this status indicates it was successfully sent.
+   */
+  const STATUS_OK = 200;
+
+  /**
+   * Status DELIVERED.
+   *
+   * A message with this status indicates it was successfully delivered.
+   */
+  const STATUS_DELIVERED = 202;
+
+  /**
+   * Status QUEUED.
+   *
+   * A message with this status indicates it was successfully queued for sending.
+   */
+  const STATUS_QUEUED = 302;
+
+  /**
+   * Status ERROR.
+   *
+   * A message with this status indicates it could not be sent (routing reasons).
+   */
+  const STATUS_ERROR = 400;
+
+  /**
+   * Status NO_CREDIT.
+   *
+   * A message with this status indicates it could not be sent due to low credit
+   * balance.
+   */
+  const STATUS_NO_CREDIT = 402;
+
+  /**
+   * Status EXPIRED.
+   *
+   * A message with this status indicates it has expired and has not been sent.
+   */
+  const STATUS_EXPIRED = 408;
+
+  /**
+   * Get the list of recipients of this sms message.
+   *
+   * @return array
+   */
+  public function getRecipients();
+
+  /**
+   * Get the options for building or sending this sms message.
+   *
+   * @return array
+   */
+  public function getOptions();
+
+  /**
+   * Get the option specified by the key $name.
+   *
+   * @param string
+   *   The name of the option.
+   * @return array
+   */
+  public function getOption($name);
+
+  /**
+   * Get the name of the sender of this sms message.
+   *
+   * @return string
+   */
+  public function getSender();
+
+  /**
+   * Get the message to be sent.
+   *
+   * @return string
+   */
+  public function getMessage();
+}
\ No newline at end of file
diff --git a/src/Message/SmsMessageResult.php b/src/Message/SmsMessageResult.php
new file mode 100644
index 0000000..c21508a
--- /dev/null
+++ b/src/Message/SmsMessageResult.php
@@ -0,0 +1,140 @@
+<?php
+
+/**
+ * Contains \Drupal\sms\Message\SmsMessageResult
+ */
+
+namespace Drupal\sms\Message;
+
+
+/**
+ * The result of an sms messaging transaction.
+ */
+class SmsMessageResult implements SmsMessageResultInterface {
+
+  /**
+   * The status of the message.
+   *
+   * @var bool
+   */
+  public $status;
+
+  /**
+   * The translated error message if status is FALSE.
+   *
+   * @var string
+   */
+  public $errorMessage;
+
+  /**
+   * The credits used for this message.
+   *
+   * @var integer
+   */
+  public $creditsUsed;
+
+  /**
+   * The credit balance after this message is sent.
+   *
+   * @var integer
+   */
+  public $creditBalance;
+
+  /**
+   * The message reports keyed by recipient number.
+   *
+   * @var array
+   */
+  public $report;
+
+  /**
+   * Create a new message result based on data supplied in the array.
+   *
+   * @param array $data
+   *   Information to be used to instantiate the SmsMessageResult.
+   */
+  public function __construct($data) {
+    $this->status = $data['status'];
+    $this->creditBalance = $data['credit_balance'];
+    $this->creditsUsed = $data['credit_used'];
+    $this->errorMessage = $data['error_message'];
+    $this->report = $data['report'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getStatus() {
+    return $this->status;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getReport() {
+    return $this->report;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getBalance() {
+    return $this->creditBalance;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCreditsUsed() {
+    return $this->creditsUsed;
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @return array(
+   *     'status'  => TRUE | FALSE,
+   *     'error_message' => '<message to display if status above is FALSE>',
+   *     'credit_used' => '<credits used>',
+   *     'credit_balance' => '<credits remaining>',
+   *     'report'  => array(
+   *       '<number>' => array(
+   *         'status' => TRUE | FALSE   // Essentially TRUE if there is a message id
+   *         'message_id' => '<message id>',
+   *         'error_code' => '<sms framework error code>',
+   *         'error_message'     => '<sms framework error description>',
+   *         'gateway_error_code'     => '<gateway error code>',
+   *         'gateway_error_message'     => '<gateway error message>',
+   *       ),
+   *   );
+   */
+  public function toArray() {
+    return array(
+      'status' => $this->status,
+      'error_message' => $this->errorMessage,
+      'credit_used' => $this->creditUsed,
+      'credit_balance' => $this->creditBalance,
+      'report' => $this->report,
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getErrorMessage() {
+    return $this->errorMessage;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getReportFor($recipient) {
+    if (isset($this->report[$recipient])) {
+      return $this->report[$recipient];
+    }
+    else {
+      return null;
+    }
+  }
+
+}
diff --git a/src/Message/SmsMessageResultInterface.php b/src/Message/SmsMessageResultInterface.php
new file mode 100644
index 0000000..737cb0b
--- /dev/null
+++ b/src/Message/SmsMessageResultInterface.php
@@ -0,0 +1,98 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\sms\Message\SmsMessageResultInterface
+ */
+
+namespace Drupal\sms\Message;
+
+/**
+ * Contains information on sms message results.
+ * @return array(
+ *     'status'  => TRUE | FALSE,
+ *     'message' => '<message to display if status above is FALSE>',
+ *     'credit used' => '<credits used>',
+ *     'credit balance' => '<credits remaining>',
+ *     'report'  => array(
+ *       '<number>' => array(
+ *         'status' => TRUE | FALSE   // Essentially TRUE if there is a message id
+ *         'error code' => '<error code>',
+ *         'error'     => '<error description>',
+ *         'error map'     => '<sms framework error code>',
+ *         'message id' => '<message id>',
+ *       ),
+ *   );
+ *
+ */
+interface SmsMessageResultInterface {
+
+  /**
+   * Gets the status of the message.
+   *
+   * @return bool
+   *   True if message was sent without errors. False if message could not be
+   *     sent.
+   */
+  public function getStatus();
+
+  /**
+   * Gets the translated error message.
+   *
+   * @return string
+   *  The translated error message if the status is FALSE.
+   */
+  public function getErrorMessage();
+
+  /**
+   * Gets the delivery report for all recipients.
+   *
+   * @return array
+   *   The report for all recipients. This report is an array keyed by the
+   *   recipients' numbers. The value is also an array with the following keys:
+   *   - status: TRUE if message was successfully sent, FALSE otherwise.
+   *   - message_id: The message id if the message was successfully sent to that
+   *       recipient. Zero means message was not sent to that recipient.
+   *   - error_code: The error code number for a specific message.
+   *   - error_message: The description of the error message.
+   *   - gateway_error_code: The original error code from the sms gateway.
+   *   - gateway_error_message: The original error message from the sms gateway.
+   */
+  public function getReport();
+
+  /**
+   * Gets the credit balance after the sms was sent.
+   *
+   * @return integer
+   *   The value of the balance. This number is in the sms gateway's chosen
+   *   denomination.
+   */
+  public function getBalance();
+
+  /**
+   * Gets the sms credits used for this transaction.
+   *
+   * @return integer
+   *   The value of the sms used in the gateway's chosen denomination.
+   */
+  public function getCreditsUsed();
+
+  /**
+   * Returns this result report as an array.
+   *
+   * @return array
+   */
+  public function toArray();
+
+  /**
+   * Gets the specific report for a particular recipient.
+   *
+   * @param string $recipient
+   *   The number of the recipient for which the report is to be retrieved.
+   * @return array
+   *   An array containing the message report
+   *   @link see SmsMessageResultInterface::getReport() @endlink
+   */
+  public function getReportFor($recipient);
+
+}
diff --git a/src/Plugin/Gateway/LogGateway.php b/src/Plugin/Gateway/LogGateway.php
new file mode 100644
index 0000000..a3a8309
--- /dev/null
+++ b/src/Plugin/Gateway/LogGateway.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\sms\Plugin\Gateway\LogGateway
+ */
+
+namespace Drupal\sms\Plugin\Gateway;
+
+use Drupal\sms\Gateway\GatewayBase;
+use Drupal\sms\Message\SmsMessageInterface;
+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',
+      array('%number' => implode(', ', $sms->getRecipients()), '@message' => $sms->getMessage()));
+//    return array('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
new file mode 100644
index 0000000..17961c6
--- /dev/null
+++ b/src/Provider/DefaultSmsProvider.php
@@ -0,0 +1,175 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\sms\Provider\DefaultSmsProvider
+ */
+
+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;
+
+/**
+ * The SMS provider that provides default messaging functionality.
+ */
+class DefaultSmsProvider implements SmsProviderInterface {
+
+  /**
+   * @var \Drupal\sms\Gateway\GatewayManager
+   *
+   * The sms gateway manager.
+   */
+  protected $gatewayManager;
+
+  /**
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   *
+   * The module handler.
+   */
+  protected $moduleHandler;
+
+  /**
+   * Create a new instance of \Drupal\sms\Sms.
+   *
+   * @param \Drupal\sms\Gateway\GatewayManagerInterface
+   *   The sms gateway manager.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface
+   *   The module handler.
+   */
+  public function __construct(GatewayManagerInterface $gateway_manager, ModuleHandlerInterface $module_handler) {
+    $this->gatewayManager = $gateway_manager;
+    $this->moduleHandler = $module_handler;
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @TODO An effective method of cascading messages and errors back up from the
+   *   gateways.
+   */
+  public function send(SmsMessageInterface $sms, array $options = array()) {
+    // Check if preferred gateway is specified in the $options.
+    if (isset($options['gateway'])) {
+      $gateway = $this->gatewayManager->getGateway($options['gateway']);
+    }
+    if (empty($gateway)) {
+      $gateway = $this->gatewayManager->getDefaultGateway();
+    }
+
+    if ($this->preProcess($sms, $options, $gateway)) {
+      $result = $this->process($sms, $options, $gateway);
+      $this->postProcess($sms, $options, $gateway, $result);
+      return $result;
+    }
+    else {
+      return FALSE;
+    }
+  }
+
+  /**
+   * Processes the sms message and handles the response from the 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);
+    $result = $this->handleResult($response, $sms);
+    return $result;
+  }
+
+  /**
+   * Calls pre-process hooks and ensures that the action is still permitted.
+   *
+   * @param \Drupal\sms\Message\SmsMessageInterface $sms
+   *   The sms to be sent.
+   * @param array $options
+   *   Additonal options to be passed to the sms gateway.
+   * @param \Drupal\sms\Gateway\GatewayInterface $gateway
+       The default gateway for sending this message.
+   *
+   * @returns bool
+   *   Whether to continue sending or not.
+   */
+  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]);
+    // Return FALSE if any of the hooks returned FALSE.
+    return !in_array(FALSE, $return);
+  }
+
+  /**
+   * Calls post process hooks.
+   *
+   * @param \Drupal\sms\Message\SmsMessageInterface $sms
+   *   The sms that was sent.
+   * @param array $options
+   *   Additonal options that were passed to the sms 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.
+   */
+  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]);
+  }
+
+  /**
+   * Handles the response back from the sms gateway.
+   *
+   * @param array
+   *   The result to be handled.
+   * @param \Drupal\sms\Message\SmsMessageInterface
+   *   The message that was sent.
+   * @return bool
+   *   True if message was sent successfully. Throws an SmsException if message
+   *   sending failed.
+   * @throws \Drupal\sms\SmsException
+   *
+   */
+  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->getErrorMessage()) {
+        $error_message .= t(' The gateway said %message.', ['%message' => $message]);
+      }
+      \Drupal::logger('sms')->error($message);
+      throw new SmsException($error_message);
+//      return FALSE;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function incoming(SmsMessageInterface $message, array $options) {
+//    if (module_exists('rules')) {
+//      $options += array('number' => $number, 'message' => $message);
+//      rules_invoke_event('sms_incoming', $options);
+//    }
+    // Execute three phases
+    $this->moduleHandler->invokeAll('sms_incoming', array('pre process', $message, $options));
+    $this->moduleHandler->invokeAll('sms_incoming', array('process', $message, $options));
+    $this->moduleHandler->invokeAll('sms_incoming', array('post process', $message, $options));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function receipt($number, $reference, $message_status = GatewayInterface::STATUS_UNKNOWN, $options = array()) {
+    // 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));
+    $this->moduleHandler->invokeAll('sms_receipt', array('post process', $number, $reference, $message_status, $options));
+  }
+
+}
diff --git a/src/Provider/SmsProviderInterface.php b/src/Provider/SmsProviderInterface.php
new file mode 100644
index 0000000..76b3bfc
--- /dev/null
+++ b/src/Provider/SmsProviderInterface.php
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * @file
+ * Contains definition of \Drupal\sms\SmsProviderInterface
+ */
+
+namespace Drupal\sms\Provider;
+
+use Drupal\sms\Gateway\GatewayInterface;
+use Drupal\sms\Message\SmsMessageInterface;
+
+/**
+ * Provides an interface for sending messages
+ */
+interface SmsProviderInterface {
+
+  /**
+   * Sends an sms using the active gateway.
+   *
+   * @param \Drupal\sms\Message\SmsMessageInterface
+   *   The message to be sent.
+   * @param array
+   *   Additional options to be passed to the sms gateway.
+   */
+  public function send(SmsMessageInterface $sms, array $options);
+
+  /**
+   * Handles a message received by the server.
+   *
+   * @param \Drupal\sms\Message\SmsMessageInterface
+   *   The message received.
+   * @param array
+   *   Additional options to be passed to the sms gateway.
+   */
+  public function incoming(SmsMessageInterface $sms, array $options);
+
+  /**
+   * Handles responses to the sms provider from gateways.
+   *
+   * Allows gateway modules to pass message receipts in a standard format for
+   * processing and provides basic set of status codes for common code handling.
+   *
+   * Allowed message status codes are defined as constants at
+   * @link \Drupal\sms\Gateway\GatewayInterface @endlink
+   *
+   * The gateway code and string will often 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 \Drupal\sms\Gateway\GatewayInterface::STATUS_UNKNOWN.
+   * @param array $options
+   *   (optional) Extended options passed by the receipt receiver.
+   */
+  public function receipt($number, $reference, $message_status = GatewayInterface::STATUS_UNKNOWN, $options = array());
+
+}
diff --git a/src/SmsException.php b/src/SmsException.php
new file mode 100644
index 0000000..c300de2
--- /dev/null
+++ b/src/SmsException.php
@@ -0,0 +1,12 @@
+<?php
+
+/**
+ * Contains \Drupal\sms\SmsException
+ */
+
+namespace Drupal\sms;
+
+/**
+ * Base exception thrown for any sms operations.
+ */
+class SmsException extends \RuntimeException {}
\ No newline at end of file
diff --git a/src/Tests/SmsFrameworkWebTest.php b/src/Tests/SmsFrameworkWebTest.php
index 9f5e772..1d7fdc6 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,12 @@ 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->getConfiguration(), 'SMS Test gateway successfully configured.');
+    $this->assertEqual($edit, $gateway->getConfiguration()['custom']);
   }
 
   /**
diff --git a/tests/modules/sms_test_gateway/sms_test_gateway.module b/tests/modules/sms_test_gateway/sms_test_gateway.module
index ad85da1..1d527a8 100644
--- a/tests/modules/sms_test_gateway/sms_test_gateway.module
+++ b/tests/modules/sms_test_gateway/sms_test_gateway.module
@@ -4,6 +4,7 @@
  * @file
  * A test gateway to be used for testing the sms framework
  */
+use Drupal\sms\Message\SmsMessageInterface;
 
 /**
  * Implements hook_gateway_info().
@@ -23,18 +24,32 @@ function sms_test_gateway_gateway_info() {
 }
 
 /**
- * Send callback
+ * Send callback.
+ *
+ * @param \Drupal\sms\Message\SmsMessageInterface $sms
+ *   The message to be sent
+ * @param array $options
+ *   Options for sending the message.
+ *
+ * @return array
  */
-function sms_test_gateway_send($number = NULL, $message = NULL, $options = array()) {
+function sms_test_gateway_send(SmsMessageInterface $sms, $options = array()) {
   $result = &drupal_static('sms_test_gateway_result');
 
-  if (!is_null($number) && !is_null($message)) {
-    $result['number'] = $number;
-    $result['message'] = $message;
+  if (!is_null($sms->getRecipients()) && !is_null($sms->getMessage())) {
+    $result['number'] = implode(',',$sms->getRecipients());
+    $result['message'] = $sms->getMessage();
     $result['options'] = $options;
     \Drupal::state()->set('sms_test_gateway_result', $result);
   }
-  return array('status' => TRUE, 'result' => $result);
+  return [
+    'status' => TRUE,
+    'result' => $result,
+    'credit_balance' => 1000,
+    'credit_used' => count($sms->getRecipients()),
+    'error_message' => '',
+    'report' => $result,
+  ];
 }
 
 function sms_test_gateway_result($reset = FALSE) {
@@ -144,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;
 }
