From b5fd728e37aa2a9c7a5f37e8dead0a95117d541b Mon Sep 17 00:00:00 2001
From: yehudah <yehudah@b8457f37-d9ea-0310-8a92-e5e31aec5664>
Date: Mon, 25 Nov 2019 09:25:43 +0000
Subject: =?UTF-8?q?phpmailer=20delivery=20improvments=20bug=20fixes=20add?=
 =?UTF-8?q?=20option=20to=20disable=20notifications=20fix=20Invalid=20?=
 =?UTF-8?q?=E2=80=9CReply-To=E2=80=9D=20e-mail=20address?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 Postman/Extensions/Admin/PostmanAdmin.php          |  31 ++
 Postman/Extensions/Admin/PostmanAdminView.php      |  86 +++
 Postman/Extensions/Core/Notifications/INotify.php  |   7 +
 .../Core/Notifications/PostmanMailNotify.php       |  14 +
 .../Core/Notifications/PostmanNotify.php           | 241 +++++++++
 .../Core/Notifications/PostmanNotifyOptions.php    |  57 ++
 .../Core/Notifications/PostmanPushoverNotify.php   |  34 ++
 .../Core/Notifications/PostmanSlackNotify.php      |  39 ++
 .../Extensions/License/EDD_SL_Plugin_Updater.php   | 585 +++++++++++++++++++++
 .../Extensions/License/PostmanLicenseHandler.php   | 422 +++++++++++++++
 .../Extensions/License/PostmanLicenseManager.php   | 102 ++++
 Postman/Phpmailer/PostsmtpMailer.php               | 147 +++---
 .../PostmanConfigurationController.php             |  69 ++-
 .../PostmanRegisterConfigurationSettings.php       |  92 +---
 .../Postman-Email-Log/PostmanEmailLogService.php   |  65 +--
 Postman/Postman-Mail/PostmanMessage.php            |   8 +-
 Postman/Postman.php                                |   6 +
 Postman/PostmanAdminController.php                 |   4 -
 Postman/PostmanInputSanitizer.php                  |  10 -
 Postman/PostmanOptions.php                         |  44 --
 Postman/PostmanWpMail.php                          |  29 +-
 Postman/notifications/INotify.php                  |   7 -
 Postman/notifications/PostmanMailNotify.php        |  14 -
 Postman/notifications/PostmanNotify.php            |  42 --
 Postman/notifications/PostmanPushoverNotify.php    |  34 --
 Postman/notifications/PostmanSlackNotify.php       |  39 --
 26 files changed, 1783 insertions(+), 445 deletions(-)
 create mode 100644 Postman/Extensions/Admin/PostmanAdmin.php
 create mode 100644 Postman/Extensions/Admin/PostmanAdminView.php
 create mode 100644 Postman/Extensions/Core/Notifications/INotify.php
 create mode 100644 Postman/Extensions/Core/Notifications/PostmanMailNotify.php
 create mode 100644 Postman/Extensions/Core/Notifications/PostmanNotify.php
 create mode 100644 Postman/Extensions/Core/Notifications/PostmanNotifyOptions.php
 create mode 100644 Postman/Extensions/Core/Notifications/PostmanPushoverNotify.php
 create mode 100644 Postman/Extensions/Core/Notifications/PostmanSlackNotify.php
 create mode 100644 Postman/Extensions/License/EDD_SL_Plugin_Updater.php
 create mode 100644 Postman/Extensions/License/PostmanLicenseHandler.php
 create mode 100644 Postman/Extensions/License/PostmanLicenseManager.php
 delete mode 100644 Postman/notifications/INotify.php
 delete mode 100644 Postman/notifications/PostmanMailNotify.php
 delete mode 100644 Postman/notifications/PostmanNotify.php
 delete mode 100644 Postman/notifications/PostmanPushoverNotify.php
 delete mode 100644 Postman/notifications/PostmanSlackNotify.php

(limited to 'Postman')

diff --git a/Postman/Extensions/Admin/PostmanAdmin.php b/Postman/Extensions/Admin/PostmanAdmin.php
new file mode 100644
index 0000000..3b61a0a
--- /dev/null
+++ b/Postman/Extensions/Admin/PostmanAdmin.php
@@ -0,0 +1,31 @@
+<?php
+if ( ! defined( 'ABSPATH' ) ) exit;
+
+class PostmanAdmin {
+
+    public function __construct()
+    {
+        $PostmanLicenseManager = PostmanLicenseManager::get_instance();
+        $extensions = $PostmanLicenseManager->get_extensions();
+
+        if ( count( $extensions ) > 0 ) {
+            add_action('admin_menu', [ $this, 'add_menu' ], 20 );
+        }
+
+    }
+
+    public function add_menu() {
+        add_submenu_page(
+            PostmanViewController::POSTMAN_MENU_SLUG,
+            __('Extensions', 'post-smtp'),
+            __('Extensions', 'post-smtp'),
+            'manage_options',
+            'post-smtp-extensions',
+            [ $this, 'render_menu' ]
+        );
+    }
+
+    public function render_menu() {
+        include_once 'PostmanAdminView.php';
+    }
+}
diff --git a/Postman/Extensions/Admin/PostmanAdminView.php b/Postman/Extensions/Admin/PostmanAdminView.php
new file mode 100644
index 0000000..c9f0509
--- /dev/null
+++ b/Postman/Extensions/Admin/PostmanAdminView.php
@@ -0,0 +1,86 @@
+<?php if ( ! defined( 'ABSPATH' ) ) exit; ?>
+
+<style>
+    .form-table .row {
+        display: flex;
+    }
+
+    .form-table .row .flex > *:not(:last-child) {
+        margin-right: 5px;
+    }
+
+    .form-table .label {
+        align-self: center;
+        font-weight: bold;
+    }
+
+    .form-table .flex {
+        display: flex;
+    }
+
+    .form-table .flex input {
+        border-radius: 3px;
+        height: 30px;
+        margin: 0;
+        margin-left: 5px;
+    }
+
+    .form-table .flex button {
+        box-shadow: none;
+        height: 100%;
+    }
+</style>
+
+<div class="wrap">
+    <h1>Post SMTP Installed Extensions</h1>
+    <form action="" method="post">
+        <div class="form-table">
+            <?php
+            $PostmanLicenseManager = PostmanLicenseManager::get_instance();
+            $extensions = $PostmanLicenseManager->get_extensions();
+
+            foreach ( $extensions as $slug => $extension) :
+                $short_name = $extension['license_manager']->get_slug( $extension['plugin_data']['Name'] );
+                $nonce = $short_name . '_license_key-nonce';
+
+                $license_data = get_option( $short_name . '_license_active' );
+                $license_key = get_option( $short_name . '_license_key' );
+
+                $license_valid = is_object( $license_data ) && $license_data->license === 'valid';
+                $license_field_class  = $license_valid ? 'readonly' : '';
+                $license_field_value  = $license_valid ? base64_encode($license_key) : '';
+
+                wp_nonce_field( $nonce, $nonce );
+                ?>
+
+                <div class="row">
+                    <div class="label">
+                        <?php echo esc_html( $extension['plugin_data']['Name'] ); ?>
+                    </div>
+
+                    <div class="flex">
+                        <div class="input">
+                            <input <?php echo $license_field_class; ?>
+                                    type="password"
+                                    name="post_smtp_extension[<?php echo $short_name . '_license_key'; ?>]"
+                                    class="regular-text"
+                                    value="<?php echo $license_field_value; ?>"
+                                    placeholder="Serial Key">
+                        </div>
+
+                        <div class="buttons">
+                            <?php if ( ! $license_valid ) :?>
+                                <button type="submit" name="post_smtp_extension[<?php echo $short_name; ?>_activate]" class="button button-primary">Activate</button>
+                            <?php endif; ?>
+
+                            <button type="submit" name="post_smtp_extension[<?php echo $short_name; ?>_deactivate]" class="button button-secondary">Deactivate</button>
+                        </div>
+                    </div>
+
+                </div>
+
+            <?php endforeach; ?>
+
+        </div>
+    </form>
+</div>
diff --git a/Postman/Extensions/Core/Notifications/INotify.php b/Postman/Extensions/Core/Notifications/INotify.php
new file mode 100644
index 0000000..f40548d
--- /dev/null
+++ b/Postman/Extensions/Core/Notifications/INotify.php
@@ -0,0 +1,7 @@
+<?php
+if ( ! defined( 'ABSPATH' ) ) {
+    exit; // Exit if accessed directly
+}
+interface Postman_Notify {
+    public function send_message( $message );
+}
\ No newline at end of file
diff --git a/Postman/Extensions/Core/Notifications/PostmanMailNotify.php b/Postman/Extensions/Core/Notifications/PostmanMailNotify.php
new file mode 100644
index 0000000..922c304
--- /dev/null
+++ b/Postman/Extensions/Core/Notifications/PostmanMailNotify.php
@@ -0,0 +1,14 @@
+<?php
+if ( ! defined( 'ABSPATH' ) ) {
+    exit; // Exit if accessed directly
+}
+class PostmanMailNotify implements Postman_Notify {
+
+    public function send_message($message)
+    {
+        $to_email = apply_filters( 'post_smtp_notify_email',get_bloginfo( 'admin_email' ) );
+        $domain = get_bloginfo( 'url' );
+
+        mail( $to_email, "{$domain}: " .  __( 'Post SMTP email error', 'post-smtp' ), $message , '', "-f{$to_email}" );
+    }
+}
\ No newline at end of file
diff --git a/Postman/Extensions/Core/Notifications/PostmanNotify.php b/Postman/Extensions/Core/Notifications/PostmanNotify.php
new file mode 100644
index 0000000..d9f6a58
--- /dev/null
+++ b/Postman/Extensions/Core/Notifications/PostmanNotify.php
@@ -0,0 +1,241 @@
+<?php
+if ( ! defined( 'ABSPATH' ) ) {
+    exit; // Exit if accessed directly
+}
+require_once 'INotify.php';
+require_once 'PostmanMailNotify.php';
+require_once 'PostmanPushoverNotify.php';
+require_once 'PostmanSlackNotify.php';
+require_once 'PostmanNotifyOptions.php';
+
+class PostmanNotify {
+
+    const NOTIFICATIONS_OPTIONS = 'postman_notifications_options';
+    const NOTIFICATIONS_SECTION = 'postman_notifications_section';
+    const NOTIFICATIONS_PUSHOVER_CRED = 'postman_pushover_cred';
+    const NOTIFICATIONS_SLACK_CRED = 'postman_slack_cred';
+
+    public function __construct() {
+
+        $this->options = new PostmanNotifyOptions();
+
+        add_filter( 'post_smtp_admin_tabs', array( $this, 'tabs' ) );
+        add_action( 'post_smtp_settings_menu', array( $this, 'menu' ) );
+        add_action( 'post_smtp_settings_fields', array( $this, 'settings' ) );
+        add_action( 'post_smtp_on_failed', array( $this, 'notify' ), 10, 5 );
+        add_filter( 'post_smtp_sanitize', array( $this, 'sanitize' ), 10, 3 );
+    }
+
+    public function menu() {
+        print '<section id="notifications">';
+        do_settings_sections( self::NOTIFICATIONS_OPTIONS );
+
+        $currentKey = $this->options->getNotificationService();
+        $pushover = $currentKey == 'pushover' ? 'block' : 'none';
+        $slack = $currentKey == 'slack' ? 'block' : 'none';
+
+        echo '<div id="pushover_cred" style="display: ' . $pushover . ';">';
+        do_settings_sections( self::NOTIFICATIONS_PUSHOVER_CRED );
+        echo '</div>';
+
+        echo '<div id="slack_cred" style="display: ' . $slack . ';">';
+        do_settings_sections( self::NOTIFICATIONS_SLACK_CRED );
+        echo '</div>';
+
+        do_action( 'post_smtp_notification_settings' );
+
+        print '</section>';
+    }
+
+    public function sanitize($new_input, $input, $sanitizer) {
+        // Notifications
+        $sanitizer->sanitizeString( 'Pushover Service', PostmanNotifyOptions::NOTIFICATION_SERVICE, $input, $new_input, $this->options->getNotificationService() );
+        $sanitizer->sanitizePassword( 'Pushover Username', PostmanNotifyOptions::PUSHOVER_USER, $input, $new_input, $this->options->getPushoverUser() );
+        $sanitizer->sanitizePassword( 'Pushover Token', PostmanNotifyOptions::PUSHOVER_TOKEN, $input, $new_input, $this->options->getPushoverToken() );
+        $sanitizer->sanitizePassword( 'Slack Token', PostmanNotifyOptions::SLACK_TOKEN, $input, $new_input, $this->options->getSlackToken() );
+
+        // Chrome extension
+        $sanitizer->sanitizeString( 'Push Chrome Extension', PostmanNotifyOptions::NOTIFICATION_USE_CHROME, $input, $new_input );
+        $sanitizer->sanitizePassword( 'Push Chrome Extension UID', PostmanNotifyOptions::NOTIFICATION_CHROME_UID, $input, $new_input, $this->options->getNotificationChromeUid() );
+
+        return $new_input;
+    }
+
+    public function tabs($tabs) {
+        $tabs['notifications'] = __( 'Notifications', 'post-smtp' );
+
+        return $tabs;
+    }
+
+    public function settings() {
+        // Notifications
+        add_settings_section( self::NOTIFICATIONS_SECTION, _x( 'Notifications Settings', 'Configuration Section Title', 'post-smtp' ), array(
+            $this,
+            'printNotificationsSectionInfo',
+        ), self::NOTIFICATIONS_OPTIONS );
+
+        add_settings_field( PostmanNotifyOptions::NOTIFICATION_SERVICE, _x( 'Notification Service', 'Configuration Input Field', 'post-smtp' ), array(
+            $this,
+            'notification_service_callback',
+        ), self::NOTIFICATIONS_OPTIONS, self::NOTIFICATIONS_SECTION );
+
+        // Pushover
+        add_settings_section( 'pushover_credentials', _x( 'Pushover Credentials', 'Configuration Section Title', 'post-smtp' ), array(
+            $this,
+            'printNotificationsSectionInfo',
+        ), self::NOTIFICATIONS_PUSHOVER_CRED );
+
+        add_settings_field( PostmanNotifyOptions::PUSHOVER_USER, _x( 'Pushover User Key', 'Configuration Input Field', 'post-smtp' ), array(
+            $this,
+            'pushover_user_callback',
+        ), self::NOTIFICATIONS_PUSHOVER_CRED, 'pushover_credentials' );
+
+        add_settings_field( PostmanNotifyOptions::PUSHOVER_TOKEN, _x( 'Pushover App Token', 'Configuration Input Field', 'post-smtp' ), array(
+            $this,
+            'pushover_token_callback',
+        ), self::NOTIFICATIONS_PUSHOVER_CRED, 'pushover_credentials' );
+
+        // Slack
+        add_settings_section( 'slack_credentials', _x( 'Slack Credentials', 'Configuration Section Title', 'post-smtp' ), array(
+            $this,
+            'printNotificationsSectionInfo',
+        ), self::NOTIFICATIONS_SLACK_CRED );
+
+        add_settings_field( PostmanNotifyOptions::SLACK_TOKEN, _x( 'Slack Webhook', 'Configuration Input Field', 'post-smtp' ), array(
+            $this,
+            'slack_token_callback',
+        ), self::NOTIFICATIONS_SLACK_CRED, 'slack_credentials' );
+
+        add_settings_field( PostmanNotifyOptions::NOTIFICATION_USE_CHROME, _x( 'Push to chrome extension', 'Configuration Input Field', 'post-smtp' ), array(
+            $this,
+            'notification_use_chrome_callback',
+        ), self::NOTIFICATIONS_OPTIONS, self::NOTIFICATIONS_SECTION );
+
+        add_settings_field( 'notification_chrome_uid', _x( 'Chrome Extension UID', 'Configuration Input Field', 'post-smtp' ), array(
+            $this,
+            'notification_chrome_uid_callback',
+        ), self::NOTIFICATIONS_OPTIONS, self::NOTIFICATIONS_SECTION );
+    }
+
+    /**
+     * Print the Section text
+     */
+    public function printNotificationsSectionInfo() {
+    }
+
+    public function notification_service_callback() {
+        $inputDescription =  __( 'Select the notification service you want to recieve alerts about failed emails.' );
+
+        $options = apply_filters('post_smtp_notification_service', array(
+            'none' => __( 'None', 'post-smtp' ),
+            'default' => __( 'WP Admin Email', 'post-smtp' ),
+            'pushover' => __( 'Pushover', 'post-smtp' ),
+            'slack' => __( 'Slack', 'post-smtp' ),
+        ));
+
+        printf( '<select id="input_%2$s" class="input_%2$s" name="%1$s[%2$s]">', 'postman_options', PostmanNotifyOptions::NOTIFICATION_SERVICE );
+        $currentKey = $this->options->getNotificationService();
+
+        foreach ( $options as $key => $label ) {
+            $this->printSelectOption( $label, $key, $currentKey );
+        }
+
+        printf( '</select><br/><span class="postman_input_description">%s</span>', $inputDescription );
+    }
+
+    public function notification_use_chrome_callback() {
+        $value = $this->options->useChromeExtension();
+        printf( '<input type="checkbox" id="input_%2$s" class="input_%2$s" name="%1$s[%2$s]" %3$s />', 'postman_options', PostmanNotifyOptions::NOTIFICATION_USE_CHROME, $value ? 'checked="checked"' : '' );
+    }
+
+    public function notification_chrome_uid_callback() {
+        printf( '<input type="password" id="input_%2$s" class="input_%2$s" name="%1$s[%2$s]" value="%3$s" />', 'postman_options', 'notification_chrome_uid', PostmanUtils::obfuscatePassword( $this->options->getNotificationChromeUid() ) );
+    }
+
+    public function pushover_user_callback() {
+        printf( '<input type="password" id="pushover_user" name="%s[%s]" value="%s" />', 'postman_options', PostmanNotifyOptions::PUSHOVER_USER, $this->options->getPushoverUser() );
+    }
+
+    public function pushover_token_callback() {
+        printf( '<input type="password" id="pushover_token" name="%s[%s]" value="%s" />', 'postman_options', PostmanNotifyOptions::PUSHOVER_TOKEN, $this->options->getPushoverToken() );
+    }
+
+    public function slack_token_callback() {
+        printf( '<input type="password" id="slack_token" name="%s[%s]" value="%s" />', 'postman_options', PostmanNotifyOptions::SLACK_TOKEN, $this->options->getSlackToken() );
+        echo '<a target="_blank" href="https://slack.postmansmtp.com/">' . __( 'Get your webhook URL here', 'post-smtp' ) . '</a>';
+
+    }
+
+    /**
+     * @param PostmanEmailLog $log
+     * @param PostmanMessage $message
+     * @param string $transcript
+     * @param PostmanTransport $transport
+     * @param string $errorMessage
+     */
+    public function notify ($log, $postmanMessage, $transcript, $transport, $errorMessage ) {
+        $message = __( 'You getting this message because an error detected while delivered your email.', 'post-smtp' );
+        $message .= "\r\n" . sprintf( __( 'For the domain: %1$s','post-smtp' ), get_bloginfo('url') );
+        $message .= "\r\n" . __( 'The log to paste when you open a support issue:', 'post-smtp' ) . "\r\n";
+
+        if ( $errorMessage && ! empty( $errorMessage ) ) {
+
+            $message = $message . $errorMessage;
+
+            $notification_service = PostmanNotifyOptions::getInstance()->getNotificationService();
+            switch ($notification_service) {
+                case 'none':
+                    $notifyer = false;
+                    break;
+                case 'default':
+                    $notifyer = new PostmanMailNotify;
+                    break;
+                case 'pushover':
+                    $notifyer = new PostmanPushoverNotify;
+                    break;
+                case 'slack':
+                    $notifyer = new PostmanSlackNotify;
+                    break;
+                default:
+                    $notifyer = new PostmanMailNotify;
+            }
+
+            $notifyer = apply_filters('post_smtp_notifier', $notifyer, $notification_service);
+
+            // Notifications
+            if ( $notifyer ) {
+                $notifyer->send_message($message, $log);
+            }
+
+            $this->push_to_chrome($errorMessage);
+        }
+    }
+
+    public function push_to_chrome($message) {
+        $push_chrome = PostmanNotifyOptions::getInstance()->useChromeExtension();
+
+        if ( $push_chrome ) {
+            $uid = PostmanNotifyOptions::getInstance()->getNotificationChromeUid();
+
+            if ( empty( $uid ) ) {
+                return;
+            }
+
+            $url = 'https://postmansmtp.com/chrome/' . $uid;
+
+            $args = array(
+                'body' => array(
+                    'message' => $message
+                )
+            );
+
+            $response = wp_remote_post( $url , $args );
+        }
+    }
+
+    private function printSelectOption( $label, $optionKey, $currentKey ) {
+        $optionPattern = '<option value="%1$s" %2$s>%3$s</option>';
+        printf( $optionPattern, $optionKey, $optionKey == $currentKey ? 'selected="selected"' : '', $label );
+    }
+}
+new PostmanNotify();
\ No newline at end of file
diff --git a/Postman/Extensions/Core/Notifications/PostmanNotifyOptions.php b/Postman/Extensions/Core/Notifications/PostmanNotifyOptions.php
new file mode 100644
index 0000000..08c27db
--- /dev/null
+++ b/Postman/Extensions/Core/Notifications/PostmanNotifyOptions.php
@@ -0,0 +1,57 @@
+<?php
+
+class PostmanNotifyOptions {
+
+    const DEFAULT_NOTIFICATION_SERVICE = 'default';
+    const NOTIFICATION_SERVICE = 'notification_service';
+    const NOTIFICATION_USE_CHROME = 'notification_use_chrome';
+    const NOTIFICATION_CHROME_UID = 'notification_chrome_uid';
+    const PUSHOVER_USER = 'pushover_user';
+    const PUSHOVER_TOKEN = 'pushover_token';
+    const SLACK_TOKEN = 'slack_token';
+
+    private $options;
+
+    public function __construct()
+    {
+        $this->options = get_option( 'postman_options' );
+    }
+
+    public function getNotificationService() {
+        if ( isset( $this->options [ self::NOTIFICATION_SERVICE ] ) ) {
+            return $this->options [ self::NOTIFICATION_SERVICE ];
+        } else {
+            return self::DEFAULT_NOTIFICATION_SERVICE;
+        }
+    }
+
+    public function getPushoverUser() {
+        if ( isset( $this->options [ self::PUSHOVER_USER ] ) ) {
+            return base64_decode( $this->options [ self::PUSHOVER_USER ] );
+        }
+    }
+
+    public function getPushoverToken() {
+        if ( isset( $this->options [ self::PUSHOVER_TOKEN ] ) ) {
+            return base64_decode( $this->options [ self::PUSHOVER_TOKEN ] );
+        }
+    }
+
+    public function getSlackToken() {
+        if ( isset( $this->options [ self::SLACK_TOKEN ] ) ) {
+            return base64_decode( $this->options [ self::SLACK_TOKEN ] );
+        }
+    }
+
+    public function useChromeExtension() {
+        if ( isset( $this->options [ self::NOTIFICATION_USE_CHROME ] ) ) {
+            return $this->options [ self::NOTIFICATION_USE_CHROME ];
+        }
+    }
+
+    public function getNotificationChromeUid() {
+        if ( isset( $this->options [ self::NOTIFICATION_CHROME_UID ] ) ) {
+            return base64_decode( $this->options [ self::NOTIFICATION_CHROME_UID ] );
+        }
+    }
+}
\ No newline at end of file
diff --git a/Postman/Extensions/Core/Notifications/PostmanPushoverNotify.php b/Postman/Extensions/Core/Notifications/PostmanPushoverNotify.php
new file mode 100644
index 0000000..14ef7d2
--- /dev/null
+++ b/Postman/Extensions/Core/Notifications/PostmanPushoverNotify.php
@@ -0,0 +1,34 @@
+<?php
+if ( ! defined( 'ABSPATH' ) ) {
+    exit; // Exit if accessed directly
+}
+class PostmanPushoverNotify implements Postman_Notify {
+
+    public function send_message($message)
+    {
+        $options = PostmanOptions::getInstance();
+
+        $api_url = "https://api.pushover.net/1/messages.json";
+        $app_token = $options->getPushoverToken();
+        $user_key = $options->getPushoverUser();
+
+        $args = array(
+            'body' => array(
+                "token" => $app_token,
+                "user" => $user_key,
+                "message" => $message,
+            )
+        );
+
+        $result = wp_remote_post( $api_url, $args );
+
+        if ( is_wp_error($result) ) {
+            error_log( __CLASS__ . ': ' . $result->get_error_message() );
+        }
+
+        $body = json_decode( wp_remote_retrieve_body( $result ), true );
+        if ( $body['status'] == 0 ) {
+            error_log( __CLASS__ . ': ' . print_r( $body, true ) );
+        }
+    }
+}
\ No newline at end of file
diff --git a/Postman/Extensions/Core/Notifications/PostmanSlackNotify.php b/Postman/Extensions/Core/Notifications/PostmanSlackNotify.php
new file mode 100644
index 0000000..5b6fae3
--- /dev/null
+++ b/Postman/Extensions/Core/Notifications/PostmanSlackNotify.php
@@ -0,0 +1,39 @@
+<?php
+if ( ! defined( 'ABSPATH' ) ) {
+    exit; // Exit if accessed directly
+}
+class PostmanSlackNotify implements Postman_Notify {
+
+    public function send_message($message)
+    {
+        $options = PostmanOptions::getInstance();
+
+        $api_url = $options->getSlackToken();
+
+        $headers = array(
+            'content-type' => 'application/json'
+        );
+
+        $body = array(
+            'text' => $message
+        );
+
+        $args = array(
+            'headers' => $headers,
+            'body' => json_encode($body)
+        );
+
+        $result = wp_remote_post( $api_url, $args );
+
+        if ( is_wp_error($result) ) {
+            error_log( __CLASS__ . ': ' . $result->get_error_message() );
+        }
+
+        $code = wp_remote_retrieve_response_code( $result );
+        $message = wp_remote_retrieve_response_message( $result );
+
+        if ( $code != 200 && $message !== 'OK' ) {
+            error_log( __CLASS__ . ': ' . $message );
+        }
+    }
+}
\ No newline at end of file
diff --git a/Postman/Extensions/License/EDD_SL_Plugin_Updater.php b/Postman/Extensions/License/EDD_SL_Plugin_Updater.php
new file mode 100644
index 0000000..0673834
--- /dev/null
+++ b/Postman/Extensions/License/EDD_SL_Plugin_Updater.php
@@ -0,0 +1,585 @@
+<?php
+
+// Exit if accessed directly
+if ( ! defined( 'ABSPATH' ) ) exit;
+
+/**
+ * Allows plugins to use their own update API.
+ *
+ * @author Easy Digital Downloads
+ * @version 1.6.19
+ */
+class EDD_SL_Plugin_Updater {
+
+	private $api_url     = '';
+	private $api_data    = array();
+	private $name        = '';
+	private $slug        = '';
+	private $version     = '';
+	private $wp_override = false;
+	private $cache_key   = '';
+
+	private $health_check_timeout = 5;
+
+	/**
+	 * Class constructor.
+	 *
+	 * @uses plugin_basename()
+	 * @uses hook()
+	 *
+	 * @param string  $_api_url     The URL pointing to the custom API endpoint.
+	 * @param string  $_plugin_file Path to the plugin file.
+	 * @param array   $_api_data    Optional data to send with API calls.
+	 */
+	public function __construct( $_api_url, $_plugin_file, $_api_data = null ) {
+
+		global $edd_plugin_data;
+
+		$this->api_url     = trailingslashit( $_api_url );
+		$this->api_data    = $_api_data;
+		$this->name        = plugin_basename( $_plugin_file );
+		$this->slug        = basename( $_plugin_file, '.php' );
+		$this->version     = $_api_data['version'];
+		$this->wp_override = isset( $_api_data['wp_override'] ) ? (bool) $_api_data['wp_override'] : false;
+		$this->beta        = ! empty( $this->api_data['beta'] ) ? true : false;
+		$this->cache_key   = 'edd_sl_' . md5( serialize( $this->slug . $this->api_data['license'] . $this->beta ) );
+
+		$edd_plugin_data[ $this->slug ] = $this->api_data;
+
+		/**
+		 * Fires after the $edd_plugin_data is setup.
+		 *
+		 * @since x.x.x
+		 *
+		 * @param array $edd_plugin_data Array of EDD SL plugin data.
+		 */
+		do_action( 'post_edd_sl_plugin_updater_setup', $edd_plugin_data );
+
+		// Set up hooks.
+		$this->init();
+
+	}
+
+	/**
+	 * Set up WordPress filters to hook into WP's update process.
+	 *
+	 * @uses add_filter()
+	 *
+	 * @return void
+	 */
+	public function init() {
+
+		add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) );
+		add_filter( 'plugins_api', array( $this, 'plugins_api_filter' ), 10, 3 );
+		remove_action( 'after_plugin_row_' . $this->name, 'wp_plugin_update_row', 10 );
+		add_action( 'after_plugin_row_' . $this->name, array( $this, 'show_update_notification' ), 10, 2 );
+		add_action( 'admin_init', array( $this, 'show_changelog' ) );
+
+	}
+
+	/**
+	 * Check for Updates at the defined API endpoint and modify the update array.
+	 *
+	 * This function dives into the update API just when WordPress creates its update array,
+	 * then adds a custom API call and injects the custom plugin data retrieved from the API.
+	 * It is reassembled from parts of the native WordPress plugin update code.
+	 * See wp-includes/update.php line 121 for the original wp_update_plugins() function.
+	 *
+	 * @uses api_request()
+	 *
+	 * @param array   $_transient_data Update array build by WordPress.
+	 * @return array Modified update array with custom plugin data.
+	 */
+	public function check_update( $_transient_data ) {
+
+		global $pagenow;
+
+		if ( ! is_object( $_transient_data ) ) {
+			$_transient_data = new stdClass;
+		}
+
+		if ( 'plugins.php' == $pagenow && is_multisite() ) {
+			return $_transient_data;
+		}
+
+		if ( ! empty( $_transient_data->response ) && ! empty( $_transient_data->response[ $this->name ] ) && false === $this->wp_override ) {
+			return $_transient_data;
+		}
+
+		$version_info = $this->get_cached_version_info();
+
+		if ( false === $version_info ) {
+			$version_info = $this->api_request( 'plugin_latest_version', array( 'slug' => $this->slug, 'beta' => $this->beta ) );
+
+			$this->set_version_info_cache( $version_info );
+
+		}
+
+		if ( false !== $version_info && is_object( $version_info ) && isset( $version_info->new_version ) ) {
+
+			if ( version_compare( $this->version, $version_info->new_version, '<' ) ) {
+
+				$_transient_data->response[ $this->name ] = $version_info;
+
+				// Make sure the plugin property is set to the plugin's name/location. See issue 1463 on Software Licensing's GitHub repo.
+				$_transient_data->response[ $this->name ]->plugin = $this->name;
+
+			}
+
+			$_transient_data->last_checked           = time();
+			$_transient_data->checked[ $this->name ] = $this->version;
+
+		}
+
+		return $_transient_data;
+	}
+
+	/**
+	 * show update nofication row -- needed for multisite subsites, because WP won't tell you otherwise!
+	 *
+	 * @param string  $file
+	 * @param array   $plugin
+	 */
+	public function show_update_notification( $file, $plugin ) {
+
+		if ( is_network_admin() ) {
+			return;
+		}
+
+		if( ! current_user_can( 'update_plugins' ) ) {
+			return;
+		}
+
+		if( ! is_multisite() ) {
+			return;
+		}
+
+		if ( $this->name != $file ) {
+			return;
+		}
+
+		// Remove our filter on the site transient
+		remove_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ), 10 );
+
+		$update_cache = get_site_transient( 'update_plugins' );
+
+		$update_cache = is_object( $update_cache ) ? $update_cache : new stdClass();
+
+		if ( empty( $update_cache->response ) || empty( $update_cache->response[ $this->name ] ) ) {
+
+			$version_info = $this->get_cached_version_info();
+
+			if ( false === $version_info ) {
+				$version_info = $this->api_request( 'plugin_latest_version', array( 'slug' => $this->slug, 'beta' => $this->beta ) );
+
+				// Since we disabled our filter for the transient, we aren't running our object conversion on banners, sections, or icons. Do this now:
+				if ( isset( $version_info->banners ) && ! is_array( $version_info->banners ) ) {
+					$version_info->banners = $this->convert_object_to_array( $version_info->banners );
+				}
+
+				if ( isset( $version_info->sections ) && ! is_array( $version_info->sections ) ) {
+					$version_info->sections = $this->convert_object_to_array( $version_info->sections );
+				}
+
+				if ( isset( $version_info->icons ) && ! is_array( $version_info->icons ) ) {
+					$version_info->icons = $this->convert_object_to_array( $version_info->icons );
+				}
+
+				if ( isset( $version_info->icons ) && ! is_array( $version_info->icons ) ) {
+					$version_info->icons = $this->convert_object_to_array( $version_info->icons );
+				}
+
+				if ( isset( $version_info->contributors ) && ! is_array( $version_info->contributors ) ) {
+					$version_info->contributors = $this->convert_object_to_array( $version_info->contributors );
+				}
+
+				$this->set_version_info_cache( $version_info );
+			}
+
+			if ( ! is_object( $version_info ) ) {
+				return;
+			}
+
+			if ( version_compare( $this->version, $version_info->new_version, '<' ) ) {
+
+				$update_cache->response[ $this->name ] = $version_info;
+
+			}
+
+			$update_cache->last_checked = time();
+			$update_cache->checked[ $this->name ] = $this->version;
+
+			set_site_transient( 'update_plugins', $update_cache );
+
+		} else {
+
+			$version_info = $update_cache->response[ $this->name ];
+
+		}
+
+		// Restore our filter
+		add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) );
+
+		if ( ! empty( $update_cache->response[ $this->name ] ) && version_compare( $this->version, $version_info->new_version, '<' ) ) {
+
+			// build a plugin list row, with update notification
+			$wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
+			# <tr class="plugin-update-tr"><td colspan="' . $wp_list_table->get_column_count() . '" class="plugin-update colspanchange">
+			echo '<tr class="plugin-update-tr" id="' . $this->slug . '-update" data-slug="' . $this->slug . '" data-plugin="' . $this->slug . '/' . $file . '">';
+			echo '<td colspan="3" class="plugin-update colspanchange">';
+			echo '<div class="update-message notice inline notice-warning notice-alt">';
+
+			$changelog_link = self_admin_url( 'index.php?edd_sl_action=view_plugin_changelog&plugin=' . $this->name . '&slug=' . $this->slug . '&TB_iframe=true&width=772&height=911' );
+
+			if ( empty( $version_info->download_link ) ) {
+				printf(
+					__( 'There is a new version of %1$s available. %2$sView version %3$s details%4$s.', 'easy-digital-downloads' ),
+					esc_html( $version_info->name ),
+					'<a target="_blank" class="thickbox" href="' . esc_url( $changelog_link ) . '">',
+					esc_html( $version_info->new_version ),
+					'</a>'
+				);
+			} else {
+				printf(
+					__( 'There is a new version of %1$s available. %2$sView version %3$s details%4$s or %5$supdate now%6$s.', 'easy-digital-downloads' ),
+					esc_html( $version_info->name ),
+					'<a target="_blank" class="thickbox" href="' . esc_url( $changelog_link ) . '">',
+					esc_html( $version_info->new_version ),
+					'</a>',
+					'<a href="' . esc_url( wp_nonce_url( self_admin_url( 'update.php?action=upgrade-plugin&plugin=' ) . $this->name, 'upgrade-plugin_' . $this->name ) ) .'">',
+					'</a>'
+				);
+			}
+
+			do_action( "in_plugin_update_message-{$file}", $plugin, $version_info );
+
+			echo '</div></td></tr>';
+		}
+	}
+
+	/**
+	 * Updates information on the "View version x.x details" page with custom data.
+	 *
+	 * @uses api_request()
+	 *
+	 * @param mixed   $_data
+	 * @param string  $_action
+	 * @param object  $_args
+	 * @return object $_data
+	 */
+	public function plugins_api_filter( $_data, $_action = '', $_args = null ) {
+
+		if ( $_action != 'plugin_information' ) {
+
+			return $_data;
+
+		}
+
+		if ( ! isset( $_args->slug ) || ( $_args->slug != $this->slug ) ) {
+
+			return $_data;
+
+		}
+
+		$to_send = array(
+			'slug'   => $this->slug,
+			'is_ssl' => is_ssl(),
+			'fields' => array(
+				'banners' => array(),
+				'reviews' => false,
+				'icons'   => array(),
+			)
+		);
+
+		$cache_key = 'edd_api_request_' . md5( serialize( $this->slug . $this->api_data['license'] . $this->beta ) );
+
+		// Get the transient where we store the api request for this plugin for 24 hours
+		$edd_api_request_transient = $this->get_cached_version_info( $cache_key );
+
+		//If we have no transient-saved value, run the API, set a fresh transient with the API value, and return that value too right now.
+		if ( empty( $edd_api_request_transient ) ) {
+
+			$api_response = $this->api_request( 'plugin_information', $to_send );
+
+			// Expires in 3 hours
+			$this->set_version_info_cache( $api_response, $cache_key );
+
+			if ( false !== $api_response ) {
+				$_data = $api_response;
+			}
+
+		} else {
+			$_data = $edd_api_request_transient;
+		}
+
+		// Convert sections into an associative array, since we're getting an object, but Core expects an array.
+		if ( isset( $_data->sections ) && ! is_array( $_data->sections ) ) {
+			$_data->sections = $this->convert_object_to_array( $_data->sections );
+		}
+
+		// Convert banners into an associative array, since we're getting an object, but Core expects an array.
+		if ( isset( $_data->banners ) && ! is_array( $_data->banners ) ) {
+			$_data->banners = $this->convert_object_to_array( $_data->banners );
+		}
+
+		// Convert icons into an associative array, since we're getting an object, but Core expects an array.
+		if ( isset( $_data->icons ) && ! is_array( $_data->icons ) ) {
+			$_data->icons = $this->convert_object_to_array( $_data->icons );
+		}
+
+		// Convert contributors into an associative array, since we're getting an object, but Core expects an array.
+		if ( isset( $_data->contributors ) && ! is_array( $_data->contributors ) ) {
+			$_data->contributors = $this->convert_object_to_array( $_data->contributors );
+		}
+
+		if( ! isset( $_data->plugin ) ) {
+			$_data->plugin = $this->name;
+		}
+
+		return $_data;
+	}
+
+	/**
+	 * Convert some objects to arrays when injecting data into the update API
+	 *
+	 * Some data like sections, banners, and icons are expected to be an associative array, however due to the JSON
+	 * decoding, they are objects. This method allows us to pass in the object and return an associative array.
+	 *
+	 * @since 3.6.5
+	 *
+	 * @param stdClass $data
+	 *
+	 * @return array
+	 */
+	private function convert_object_to_array( $data ) {
+		$new_data = array();
+		foreach ( $data as $key => $value ) {
+			$new_data[ $key ] = is_object( $value ) ? $this->convert_object_to_array( $value ) : $value;
+		}
+
+		return $new_data;
+	}
+
+	/**
+	 * Disable SSL verification in order to prevent download update failures
+	 *
+	 * @param array   $args
+	 * @param string  $url
+	 * @return object $array
+	 */
+	public function http_request_args( $args, $url ) {
+
+		$verify_ssl = $this->verify_ssl();
+		if ( strpos( $url, 'https://' ) !== false && strpos( $url, 'edd_action=package_download' ) ) {
+			$args['sslverify'] = $verify_ssl;
+		}
+		return $args;
+
+	}
+
+	/**
+	 * Calls the API and, if successfull, returns the object delivered by the API.
+	 *
+	 * @uses get_bloginfo()
+	 * @uses wp_remote_post()
+	 * @uses is_wp_error()
+	 *
+	 * @param string  $_action The requested action.
+	 * @param array   $_data   Parameters for the API action.
+	 * @return false|object
+	 */
+	private function api_request( $_action, $_data ) {
+
+		global $wp_version, $edd_plugin_url_available;
+
+		$verify_ssl = $this->verify_ssl();
+
+		// Do a quick status check on this domain if we haven't already checked it.
+		$store_hash = md5( $this->api_url );
+		if ( ! is_array( $edd_plugin_url_available ) || ! isset( $edd_plugin_url_available[ $store_hash ] ) ) {
+			$test_url_parts = parse_url( $this->api_url );
+
+			$scheme = ! empty( $test_url_parts['scheme'] ) ? $test_url_parts['scheme']     : 'http';
+			$host   = ! empty( $test_url_parts['host'] )   ? $test_url_parts['host']       : '';
+			$port   = ! empty( $test_url_parts['port'] )   ? ':' . $test_url_parts['port'] : '';
+
+			if ( empty( $host ) ) {
+				$edd_plugin_url_available[ $store_hash ] = false;
+			} else {
+				$test_url = $scheme . '://' . $host . $port;
+				$response = wp_remote_get( $test_url, array( 'timeout' => $this->health_check_timeout, 'sslverify' => $verify_ssl ) );
+				$edd_plugin_url_available[ $store_hash ] = is_wp_error( $response ) ? false : true;
+			}
+		}
+
+		if ( false === $edd_plugin_url_available[ $store_hash ] ) {
+			return;
+		}
+
+		$data = array_merge( $this->api_data, $_data );
+
+		if ( $data['slug'] != $this->slug ) {
+			return;
+		}
+
+		if( $this->api_url == trailingslashit ( home_url() ) ) {
+			return false; // Don't allow a plugin to ping itself
+		}
+
+		$api_params = array(
+			'edd_action' => 'get_version',
+			'license'    => ! empty( $data['license'] ) ? $data['license'] : '',
+			'item_name'  => isset( $data['item_name'] ) ? $data['item_name'] : false,
+			'item_id'    => isset( $data['item_id'] ) ? $data['item_id'] : false,
+			'version'    => isset( $data['version'] ) ? $data['version'] : false,
+			'slug'       => $data['slug'],
+			'author'     => $data['author'],
+			'url'        => home_url(),
+			'beta'       => ! empty( $data['beta'] ),
+		);
+
+		$request    = wp_remote_post( $this->api_url, array( 'timeout' => 15, 'sslverify' => $verify_ssl, 'body' => $api_params ) );
+
+		if ( ! is_wp_error( $request ) ) {
+			$request = json_decode( wp_remote_retrieve_body( $request ) );
+		}
+
+		if ( $request && isset( $request->sections ) ) {
+			$request->sections = maybe_unserialize( $request->sections );
+		} else {
+			$request = false;
+		}
+
+		if ( $request && isset( $request->banners ) ) {
+			$request->banners = maybe_unserialize( $request->banners );
+		}
+
+		if ( $request && isset( $request->icons ) ) {
+			$request->icons = maybe_unserialize( $request->icons );
+		}
+
+		if( ! empty( $request->sections ) ) {
+			foreach( $request->sections as $key => $section ) {
+				$request->$key = (array) $section;
+			}
+		}
+
+		return $request;
+	}
+
+	public function show_changelog() {
+
+		global $edd_plugin_data;
+
+		if( empty( $_REQUEST['edd_sl_action'] ) || 'view_plugin_changelog' != $_REQUEST['edd_sl_action'] ) {
+			return;
+		}
+
+		if( empty( $_REQUEST['plugin'] ) ) {
+			return;
+		}
+
+		if( empty( $_REQUEST['slug'] ) ) {
+			return;
+		}
+
+		if( ! current_user_can( 'update_plugins' ) ) {
+			wp_die( __( 'You do not have permission to install plugin updates', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) );
+		}
+
+		$data         = $edd_plugin_data[ $_REQUEST['slug'] ];
+		$beta         = ! empty( $data['beta'] ) ? true : false;
+		$cache_key    = md5( 'edd_plugin_' . sanitize_key( $_REQUEST['plugin'] ) . '_' . $beta . '_version_info' );
+		$version_info = $this->get_cached_version_info( $cache_key );
+
+		if( false === $version_info ) {
+
+			$api_params = array(
+				'edd_action' => 'get_version',
+				'item_name'  => isset( $data['item_name'] ) ? $data['item_name'] : false,
+				'item_id'    => isset( $data['item_id'] ) ? $data['item_id'] : false,
+				'slug'       => $_REQUEST['slug'],
+				'author'     => $data['author'],
+				'url'        => home_url(),
+				'beta'       => ! empty( $data['beta'] )
+			);
+
+			$verify_ssl = $this->verify_ssl();
+			$request    = wp_remote_post( $this->api_url, array( 'timeout' => 15, 'sslverify' => $verify_ssl, 'body' => $api_params ) );
+
+			if ( ! is_wp_error( $request ) ) {
+				$version_info = json_decode( wp_remote_retrieve_body( $request ) );
+			}
+
+
+			if ( ! empty( $version_info ) && isset( $version_info->sections ) ) {
+				$version_info->sections = maybe_unserialize( $version_info->sections );
+			} else {
+				$version_info = false;
+			}
+
+			if( ! empty( $version_info ) ) {
+				foreach( $version_info->sections as $key => $section ) {
+					$version_info->$key = (array) $section;
+				}
+			}
+
+			$this->set_version_info_cache( $version_info, $cache_key );
+
+		}
+
+		if( ! empty( $version_info ) && isset( $version_info->sections['changelog'] ) ) {
+			echo '<div style="background:#fff;padding:10px;">' . $version_info->sections['changelog'] . '</div>';
+		}
+
+		exit;
+	}
+
+	public function get_cached_version_info( $cache_key = '' ) {
+
+		if( empty( $cache_key ) ) {
+			$cache_key = $this->cache_key;
+		}
+
+		$cache = get_option( $cache_key );
+
+		if( empty( $cache['timeout'] ) || time() > $cache['timeout'] ) {
+			return false; // Cache is expired
+		}
+
+		// We need to turn the icons into an array, thanks to WP Core forcing these into an object at some point.
+		$cache['value'] = json_decode( $cache['value'] );
+		if ( ! empty( $cache['value']->icons ) ) {
+			$cache['value']->icons = (array) $cache['value']->icons;
+		}
+
+		return $cache['value'];
+
+	}
+
+	public function set_version_info_cache( $value = '', $cache_key = '' ) {
+
+		if( empty( $cache_key ) ) {
+			$cache_key = $this->cache_key;
+		}
+
+		$data = array(
+			'timeout' => strtotime( '+3 hours', time() ),
+			'value'   => json_encode( $value )
+		);
+
+		update_option( $cache_key, $data, 'no' );
+
+	}
+
+	/**
+	 * Returns if the SSL of the store should be verified.
+	 *
+	 * @since  1.6.13
+	 * @return bool
+	 */
+	private function verify_ssl() {
+		return (bool) apply_filters( 'edd_sl_api_request_verify_ssl', true, $this );
+	}
+
+}
diff --git a/Postman/Extensions/License/PostmanLicenseHandler.php b/Postman/Extensions/License/PostmanLicenseHandler.php
new file mode 100644
index 0000000..b24aa14
--- /dev/null
+++ b/Postman/Extensions/License/PostmanLicenseHandler.php
@@ -0,0 +1,422 @@
+<?php
+
+// Exit if accessed directly
+if ( ! defined( 'ABSPATH' ) ) exit;
+
+if ( ! class_exists( 'PostmanLicenseHandler' ) ) :
+
+
+class PostmanLicenseHandler {
+    private $file;
+    private $license;
+    private $license_data;
+    private $item_name;
+    private $item_id;
+    private $item_shortname;
+    private $version;
+    private $author;
+	private $api_url = 'http://localhost/psp/';
+
+
+	function __construct( $_file, $_item_name, $_version, $_author, $_optname = null, $_api_url = null, $_item_id = null ) {
+		$this->file = $_file;
+		$this->item_name = $_item_name;
+
+		if ( is_numeric( $_item_id ) ) {
+			$this->item_id = absint( $_item_id );
+		}
+
+		$this->item_shortname = $this->get_slug();
+		$this->version        = $_version;
+		$this->license        = trim( get_option( $this->item_shortname . '_license_key', '' ) );
+		$this->license_data        = get_option( $this->item_shortname . '_license_active', '' );
+		$this->author         = $_author;
+		$this->api_url        = is_null( $_api_url ) ? $this->api_url : $_api_url;
+
+		/**
+		 * Allows for backwards compatibility with old license options,
+		 * i.e. if the plugins had license key fields previously, the license
+		 * handler will automatically pick these up and use those in lieu of the
+		 * user having to reactive their license.
+		 */
+		if ( ! empty( $_optname ) ) {
+			$opt = get_option( $_optname, false );
+
+			if( isset( $opt ) && empty( $this->license ) ) {
+				$this->license = trim( $opt );
+			}
+		}
+
+		// Setup hooks
+		$this->includes();
+		$this->hooks();
+
+	}
+
+	/**
+	 * Include the updater class
+	 *
+	 * @access  private
+	 * @return  void
+	 */
+	private function includes() {
+		if ( ! class_exists( 'EDD_SL_Plugin_Updater' ) )  {
+			require_once 'EDD_SL_Plugin_Updater.php';
+		}
+	}
+
+	/**
+	 * Setup hooks
+	 *
+	 * @access  private
+	 * @return  void
+	 */
+	public function hooks() {
+
+		// Activate license key on settings save
+		add_action( 'admin_init', array( $this, 'activate_license' ) );
+
+		// Deactivate license key
+		add_action( 'admin_init', array( $this, 'deactivate_license' ) );
+
+		add_action( 'init', array( $this, 'cron' ), 20 );
+
+		// Check that license is valid once per week
+        add_action( 'admin_init', array( $this, 'validate_license' ) );
+
+		// Updater
+		add_action( 'admin_init', array( $this, 'auto_updater' ), 0 );
+
+		// Display notices to admins
+		add_action( 'admin_notices', array( $this, 'notices' ) );
+
+		add_action( 'in_plugin_update_message-' . plugin_basename( $this->file ), array( $this, 'plugin_row_license_missing' ), 10, 2 );
+	}
+
+    /**
+     * Auto updater
+     *
+     * @access  private
+     * @return  void
+     */
+    public function auto_updater() {
+
+        $args = array(
+            'version'   => $this->version,
+            'license'   => $this->license,
+            'author'    => $this->author,
+            'beta'      => function_exists( 'edd_extension_has_beta_support' ) && edd_extension_has_beta_support( $this->item_shortname ),
+        );
+
+        if( ! empty( $this->item_id ) ) {
+            $args['item_id']   = $this->item_id;
+        } else {
+            $args['item_name'] = $this->item_name;
+        }
+
+        // Setup the updater
+        $edd_updater = new EDD_SL_Plugin_Updater(
+            $this->api_url,
+            $this->file,
+            $args
+        );
+    }
+
+    public function cron() {
+        if ( ! wp_next_scheduled( $this->item_shortname . '_scheduled_events' ) ) {
+            wp_schedule_event( current_time( 'timestamp', true ), 'daily', $this->item_shortname . '_scheduled_events' );
+        }
+    }
+
+    public function get_slug() {
+        return preg_replace( '/[^a-zA-Z0-9_\s]/', '', str_replace( ' ', '_', strtolower( $this->item_name ) ) );
+    }
+
+	/**
+	 * Display help text at the top of the Licenses tag
+	 *
+	 * @since   2.5
+	 * @param   string   $active_tab
+	 * @return  void
+	 */
+	public function license_help_text( $active_tab = '' ) {
+
+		static $has_ran;
+
+		if( 'licenses' !== $active_tab ) {
+			return;
+		}
+
+		if( ! empty( $has_ran ) ) {
+			return;
+		}
+
+		echo '<p>' . sprintf(
+			__( 'Enter your extension license keys here to receive updates for purchased extensions. If your license key has expired, please <a href="%s" target="_blank">renew your license</a>.', 'easy-digital-downloads' ),
+			'http://docs.easydigitaldownloads.com/article/1000-license-renewal'
+		) . '</p>';
+
+		$has_ran = true;
+
+	}
+
+
+	/**
+	 * Activate the license key
+	 *
+	 * @return  void
+	 */
+	public function activate_license() {
+
+		if ( ! isset( $_POST['post_smtp_extension'][ $this->item_shortname . '_activate'] ) ) {
+			return;
+		}
+
+		if ( ! isset( $_REQUEST[ $this->item_shortname . '_license_key-nonce'] ) || ! wp_verify_nonce( $_REQUEST[ $this->item_shortname . '_license_key-nonce'], $this->item_shortname . '_license_key-nonce' ) ) {
+
+			return;
+
+		}
+
+		if ( ! current_user_can( 'manage_options' ) ) {
+			return;
+		}
+
+		if ( empty( $_POST['post_smtp_extension'][ $this->item_shortname . '_license_key'] ) ) {
+
+			delete_option( $this->item_shortname . '_license_active' );
+            delete_option( $this->item_shortname . '_license_key' );
+
+			return;
+
+		}
+
+		foreach ( $_POST as $key => $value ) {
+			if( false !== strpos( $key, 'license_key_deactivate' ) ) {
+				// Don't activate a key when deactivating a different key
+				return;
+			}
+		}
+
+		$details = get_option( $this->item_shortname . '_license_active' );
+
+		if ( is_object( $details ) && 'valid' === $details->license ) {
+			return;
+		}
+
+		$license = sanitize_text_field( $_POST['post_smtp_extension'][ $this->item_shortname . '_license_key'] );
+
+		if( empty( $license ) ) {
+			return;
+		}
+
+		// Data to send to the API
+		$api_params = array(
+			'edd_action' => 'activate_license',
+			'license'    => $license,
+			'item_name'  => urlencode( $this->item_name ),
+			'url'        => home_url()
+		);
+
+		if ( ! empty( $this->item_id ) ) {
+			$api_params['item_id'] = $this->item_id;
+		}
+
+		// Call the API
+		$response = wp_remote_post(
+			$this->api_url,
+			array(
+				'timeout'   => 15,
+				'sslverify' => false,
+				'body'      => $api_params
+			)
+		);
+
+		// Make sure there are no errors
+		if ( is_wp_error( $response ) ) {
+			return;
+		}
+
+		// Tell WordPress to look for updates
+		set_site_transient( 'update_plugins', null );
+
+		// Decode license data
+		$license_data = json_decode( wp_remote_retrieve_body( $response ) );
+
+		update_option( $this->item_shortname . '_license_active', $license_data );
+		update_option( $this->item_shortname . '_license_key', $license );
+
+		$slug = plugin_basename($this->file);
+        PostmanLicenseManager::get_instance()->add_extension($slug);
+
+	}
+
+
+	/**
+	 * Deactivate the license key
+	 *
+	 * @return  void
+	 */
+	public function deactivate_license() {
+
+        if ( ! isset( $_POST['post_smtp_extension'][ $this->item_shortname . '_deactivate'] ) ) {
+            return;
+        }
+
+		if ( ! isset( $_POST['post_smtp_extension'][ $this->item_shortname . '_license_key'] ) )
+			return;
+
+		if( ! wp_verify_nonce( $_REQUEST[ $this->item_shortname . '_license_key-nonce'], $this->item_shortname . '_license_key-nonce' ) ) {
+
+			wp_die( __( 'Nonce verification failed', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) );
+
+		}
+
+		if( ! current_user_can( 'manage_options' ) ) {
+			return;
+		}
+
+		$license_key = sanitize_text_field( base64_decode( $_POST['post_smtp_extension'][ $this->item_shortname . '_license_key'] ) );
+
+		// Run on deactivate button press
+        // Data to send to the API
+        $api_params = array(
+            'edd_action' => 'deactivate_license',
+            'license'    => $license_key,
+            'item_name'  => urlencode( $this->item_name ),
+            'url'        => home_url()
+        );
+
+        if ( ! empty( $this->item_id ) ) {
+            $api_params['item_id'] = $this->item_id;
+        }
+
+        // Call the API
+        $response = wp_remote_post(
+            $this->api_url,
+            array(
+                'timeout'   => 15,
+                'sslverify' => false,
+                'body'      => $api_params
+            )
+        );
+
+        // Make sure there are no errors
+        if ( is_wp_error( $response ) ) {
+            return;
+        }
+
+        // Decode the license data
+        $license_data = json_decode( wp_remote_retrieve_body( $response ) );
+
+        delete_option( $this->item_shortname . '_license_active' );
+        delete_option( $this->item_shortname . '_license_key' );
+
+        $slug = plugin_basename($this->file);
+        PostmanLicenseManager::get_instance()->remove_extension($slug);
+
+	}
+
+	public function validate_license() {
+        if ( false === ( $cron_data = get_transient( $this->item_shortname . '_cron' ) ) ) {
+            $this->license_check();
+
+            set_transient( $this->item_shortname . '_cron', true, rand( 12, 48 ) *  HOUR_IN_SECONDS );
+        }
+    }
+
+
+	/**
+	 * Check if license key is valid once per week
+	 *
+	 * @since   2.5
+	 * @return  void
+	 */
+	public function license_check() {
+
+		// data to send in our API request
+		$api_params = array(
+			'edd_action'=> 'check_license',
+			'license' 	=> $this->license,
+			'item_name' => urlencode( $this->item_name ),
+			'url'       => home_url()
+		);
+
+		// Call the API
+		$response = wp_remote_post(
+			$this->api_url,
+			array(
+				'timeout'   => 15,
+				'sslverify' => false,
+				'body'      => $api_params
+			)
+		);
+
+		// make sure the response came back okay
+		if ( is_wp_error( $response ) ) {
+			return false;
+		}
+
+		$license_data = json_decode( wp_remote_retrieve_body( $response ) );
+
+		update_option( $this->item_shortname . '_license_active', $license_data );
+
+	}
+
+
+	/**
+	 * Admin notices for errors
+	 *
+	 * @return  void
+	 */
+	public function notices() {
+
+		$showed_invalid_message = null;
+
+		if( empty( $this->license ) ) {
+			return;
+		}
+
+		if( ! current_user_can( 'manage_options' ) ) {
+			return;
+		}
+
+		$messages = array();
+
+		$license = get_option( $this->item_shortname . '_license_active' );
+
+		if( is_object( $license ) && 'valid' !== $license->license && empty( $showed_invalid_message ) ) {
+
+			if( isset( $_GET['page'] ) && 'post-smtp-extensions' === $_GET['page'] ) {
+
+				$messages[] = sprintf(
+					__( '%s has invalid or expired license key for Post SMTP.'),
+					'<strong>' . $this->item_name . '</strong>'
+				);
+
+				$showed_invalid_message = true;
+
+			}
+
+		}
+
+		if( ! empty( $messages ) ) {
+
+			foreach( $messages as $message ) {
+
+				echo '<div class="error">';
+					echo '<p>' . $message . '</p>';
+				echo '</div>';
+
+			}
+
+		}
+
+	}
+
+	public function is_licensed() {
+	    return is_object($this->license_data) && 'valid' === $this->license_data->license;
+    }
+}
+
+endif; // end class_exists check
diff --git a/Postman/Extensions/License/PostmanLicenseManager.php b/Postman/Extensions/License/PostmanLicenseManager.php
new file mode 100644
index 0000000..280b564
--- /dev/null
+++ b/Postman/Extensions/License/PostmanLicenseManager.php
@@ -0,0 +1,102 @@
+<?php
+if ( ! defined( 'ABSPATH' ) ) exit;
+
+class PostmanLicenseManager {
+
+    const ENDPOINT = 'https://postmansmtp.com';
+
+    const CORE_EXTENSIONS = [ 'gmail_api', 'sendgrid_api', 'mandrill_api', 'mailgun_api' ];
+
+    private $extensions;
+
+    private $rand_cache_interval = 12;
+
+    private static $instance;
+
+    public static function get_instance() {
+        if ( ! self::$instance ) {
+            self::$instance = new static();
+        }
+
+        return self::$instance;
+    }
+
+    /**
+     * PostmanLicenseManager constructor.
+     */
+    private function __construct()
+    {
+        $this->includes();
+        $this->rand_cache_interval = rand( 1, 24 );
+
+        add_filter( 'extra_plugin_headers', [ $this, 'add_extension_headers' ] );
+    }
+
+    public function includes() {
+        include_once 'PostmanLicenseHandler.php';
+
+        include_once ABSPATH . '/wp-admin/includes/plugin.php';
+
+    }
+
+
+    function add_extension_headers($headers) {
+        $headers[] = 'Class';
+        $headers[] = 'Slug';
+
+        return $headers;
+    }
+
+    /**
+     * Init
+     */
+    public function init() {
+
+        $plugins = get_plugins();
+        foreach ( $plugins as $plugin_dir_and_filename => $plugin_data ) {
+
+            if ( ! is_plugin_active( $plugin_dir_and_filename ) ) {
+                continue;
+            }
+
+            if ( false !== strpos( $plugin_dir_and_filename, 'post-smtp-extension' ) ) {
+                $slug = $plugin_dir_and_filename;
+                $class = $plugin_data['Class'];
+                $plugin_path = WP_CONTENT_DIR . '/plugins/' . $plugin_dir_and_filename;
+
+                $this->extensions[$slug]['plugin_data'] = $plugin_data;
+                $this->extensions[$slug]['plugin_dir_and_filename'] = $plugin_dir_and_filename;
+                $this->extensions[$slug]['license_manager'] = new PostmanLicenseHandler(
+                    $plugin_path, $plugin_data['Name'],
+                    $plugin_data['Version'], $plugin_data['Author']
+                );
+                if ( $this->extensions[$slug]['license_manager']->is_licensed() ) {
+                    include_once $plugin_path;
+
+                    $this->extensions[$slug]['instance'] = new $class;
+                }
+            }
+        }
+
+        if ( ! empty( $this->extensions ) ) {
+            new PostmanAdmin();
+        }
+    }
+
+    public function add_extension($slug) {
+        $plugin_path = WP_CONTENT_DIR . '/plugins/' . $this->extensions[$slug]['plugin_dir_and_filename'];
+        $class = $this->extensions[$slug]['plugin_data']['Class'];
+
+        include_once $plugin_path;
+        $this->extensions[$slug]['instance'] = new $class;
+    }
+
+    public function remove_extension($slug) {
+        $this->extensions[$slug]['instance'] = null;
+        unset($this->extensions[$slug]['instance']);
+    }
+
+    public function get_extensions() {
+        return $this->extensions;
+    }
+}
\ No newline at end of file
diff --git a/Postman/Phpmailer/PostsmtpMailer.php b/Postman/Phpmailer/PostsmtpMailer.php
index b3a421b..e52091d 100644
--- a/Postman/Phpmailer/PostsmtpMailer.php
+++ b/Postman/Phpmailer/PostsmtpMailer.php
@@ -2,8 +2,10 @@
 if ( ! defined( 'ABSPATH' ) ) {
     exit; // Exit if accessed directly
 }
-require_once ABSPATH . WPINC . '/class-phpmailer.php';
-require_once ABSPATH . WPINC . '/class-smtp.php';
+
+if ( ! class_exists( 'PHPMailer', false ) ) {
+    require_once ABSPATH . WPINC . '/class-phpmailer.php';
+}
 
 add_action('plugins_loaded', function() {
     global $phpmailer;
@@ -17,12 +19,53 @@ class PostsmtpMailer extends PHPMailer {
 
     private $error;
 
+    private $transcript = '';
+
     public function __construct($exceptions = null)
     {
         parent::__construct($exceptions);
 
+        $this->set_vars();
+        $this->hooks();
+
+    }
+
+    public function set_vars() {
         $this->options = PostmanOptions::getInstance();
-        add_filter( 'postman_wp_mail_result', [ $this, 'postman_wp_mail_result' ] );
+        $this->Debugoutput = function($str, $level) {
+            $this->transcript .= $str;
+        };
+    }
+
+    public function hooks() {
+        if ( $this->options->getTransportType() == 'smtp' ) {
+            add_action( 'phpmailer_init', array( $this, 'phpmailer_smtp_init' ) );
+        }
+    }
+
+    /**
+     * @param PHPMailer $mail
+     */
+    public function phpmailer_smtp_init($mail) {
+        $mail->SMTPDebug = 3;
+        $mail->isSMTP();
+        $mail->Host = $this->options->getHostname();
+
+        if ( $this->options->getAuthenticationType() !== 'none' ) {
+            $mail->SMTPAuth   = true;
+            $mail->Username   = $this->options->getUsername();
+            $mail->Password   = $this->options->getPassword();
+        }
+
+        if ( $this->options->getEncryptionType() !== 'none' ) {
+            $mail->SMTPSecure = $this->options->getEncryptionType();
+        }
+
+        $mail->Port = $this->options->getPort();
+
+        if ( $this->options->isPluginSenderEmailEnforced() ) {
+            $mail->setFrom( $this->options->getMessageSenderEmail() , $this->options->getMessageSenderName () );
+        }
     }
 
     public function send()
@@ -33,40 +76,46 @@ class PostsmtpMailer extends PHPMailer {
         $postmanWpMail = new PostmanWpMail();
         $postmanWpMail->init();
 
-        $senderEmail = $this->options->getMessageSenderEmail();
-        $senderName = $this->options->getMessageSenderName();
-
-        // create a PostmanMessage instance
-        $message = $postmanWpMail->createNewMessage();
-
-        $message->setFrom( $senderEmail, $senderName );
-        $message->addHeaders( $this->getHeaders() );
-        $message->setBodyTextPart( $this->AltBody );
-        $message->setBodyHtmlPart( $this->Body );
-        $message->setBody( $this->Body );
-        $message->setSubject( $this->Subject );
-        $message->addTo( $this->flatArray($this->getToAddresses() ) );
-        $message->setReplyTo( $this->flatArray( $this->getReplyToAddresses() ) );
-        $message->addCc( $this->flatArray($this->getCcAddresses() ) );
-        $message->addBCc( $this->flatArray( $this->getBccAddresses() ) );
-        $message->setReplyTo( $this->flatArray( $this->getReplyToAddresses() ) );
-        $message->setAttachments( $this->getAttachments() );
-
-        // create a PostmanEmailLog instance
+        $backtrace = debug_backtrace();
+
+        list($to, $subject, $body, $headers, $attachments) = array_pad( $backtrace[1]['args'], 5, null );
+
+        // build the message
+        $postmanMessage = $postmanWpMail->processWpMailCall( $to, $subject, $body, $headers, $attachments );
+
+        // build the email log entry
         $log = new PostmanEmailLog();
+        $log->originalTo = $to;
+        $log->originalSubject = $subject;
+        $log->originalMessage = $body;
+        $log->originalHeaders = $headers;
 
-        $log->originalTo = $this->flatArray($this->getToAddresses() );
-        $log->originalSubject = $this->Subject;
-        $log->originalMessage = $this->Body;
-        $log->originalHeaders = $this->getCustomHeaders();
+        // get the transport and create the transportConfig and engine
+        $transport = PostmanTransportRegistry::getInstance()->getActiveTransport();
+
+        add_filter( 'postman_wp_mail_result', [ $this, 'postman_wp_mail_result' ] );
 
         try {
-            return $postmanWpMail->sendMessage( $message, $log );
+
+            if ( $send_email = apply_filters( 'post_smtp_do_send_email', true ) ) {
+                $result = $this->options->getTransportType() !== 'smtp' ?
+                    $postmanWpMail->send( $to, $subject, $body, $headers, $attachments ) :
+                    $this->sendSmtp();
+            }
+
+
+            do_action( 'post_smtp_on_success', $log, $postmanMessage, $this->transcript, $transport );
+
+            return $result;
+
         } catch (phpmailerException $exc) {
 
             $this->error = $exc;
 
             $this->mailHeader = '';
+
+            do_action( 'post_smtp_on_failed', $log, $postmanMessage, $this->transcript, $transport, $exc->getMessage() );
+
             $this->setError($exc->getMessage());
             if ($this->exceptions) {
                 throw $exc;
@@ -76,50 +125,20 @@ class PostsmtpMailer extends PHPMailer {
 
     }
 
-    public function getAttachments() {
-        $attachments = parent::getAttachments();
-
-        $data = array();
-        foreach ( $attachments as $attachment ) {
-            $data[] = $attachment[0];
+    public function sendSmtp() {
+        if (!$this->preSend()) {
+            return false;
         }
-
-        return $data;
+        return $this->postSend();
     }
 
-    private function getHeaders() {
-        $headers = array();
-        foreach ( $this->getCustomHeaders() as $header ) {
-            $headers[] = "{$header[0]}: {$header[1]}";
-        }
 
-        return $headers;
-    }
-
-    public function postman_wp_mail_result() {
+    public  function postman_wp_mail_result() {
         $result = [
             'time' => '',
             'exception' => $this->error,
-            'transcript' => '',
+            'transcript' => $this->transcript,
         ];
         return $result;
     }
-
-    private function flatArray($arr) {
-        $result = [];
-        foreach ( $arr as $key => $value ) {
-            if ( is_array( $value ) ) {
-                foreach ($value as $k => $v ) {
-                    if ( empty( $v ) ) {
-                        continue;
-                    }
-                    $value = $v;
-                }
-            }
-
-            $result[] = $value;
-        }
-
-        return implode(',', $result );
-    }
 }
\ No newline at end of file
diff --git a/Postman/Postman-Configuration/PostmanConfigurationController.php b/Postman/Postman-Configuration/PostmanConfigurationController.php
index 59fbdb8..c63d6a9 100644
--- a/Postman/Postman-Configuration/PostmanConfigurationController.php
+++ b/Postman/Postman-Configuration/PostmanConfigurationController.php
@@ -201,13 +201,21 @@ class PostmanConfigurationController {
 		print '<div class="wrap">';
 
 		PostmanViewController::outputChildPageHeader( __( 'Settings', 'post-smtp' ), 'advanced_config' );
+
+		$config_tabs = apply_filters( 'post_smtp_admin_tabs', array(
+		    'account_config' => __( 'Account', 'post-smtp' ),
+		    'fallback' => __( 'Fallback', 'post-smtp' ),
+		    'message_config' => __( 'Message', 'post-smtp' ),
+		    'logging_config' => __( 'Logging', 'post-smtp' ),
+		    'advanced_options_config' => __( 'Advanced', 'post-smtp' ),
+        ) );
+
 		print '<div id="config_tabs"><ul>';
-		print sprintf( '<li><a href="#account_config">%s</a></li>', __( 'Account', 'post-smtp' ) );
-		print sprintf( '<li><a href="#fallback">%s</a></li>', __( 'Fallback', 'post-smtp' ) );
-		print sprintf( '<li><a href="#message_config">%s</a></li>', __( 'Message', 'post-smtp' ) );
-		print sprintf( '<li><a href="#logging_config">%s</a></li>', __( 'Logging', 'post-smtp' ) );
-		print sprintf( '<li><a href="#advanced_options_config">%s</a></li>', __( 'Advanced', 'post-smtp' ) );
-		print sprintf( '<li><a href="#notifications">%s</a></li>', __( 'Notifications', 'post-smtp' ) );
+
+		foreach ( $config_tabs as $slug => $tab ) :
+            printf( '<li><a href="#%s">%s</a></li>', $slug, $tab );
+        endforeach;
+
 		print '</ul>';
 
 		print '<form method="post" action="options.php">';
@@ -294,9 +302,9 @@ class PostmanConfigurationController {
                     <th scope="row"><?php _e('Security', 'post-smtp' ); ?></th>
                     <?php
                     $security_options = array(
-                            'none' => __( 'None', 'post-smtp' ),
-                            'ssl' => __( 'SSL', 'post-smtp' ),
-                            'tls' => __( 'TLS', 'post-smtp' ),
+                        'none' => __( 'None', 'post-smtp' ),
+                        'ssl' => __( 'SSL', 'post-smtp' ),
+                        'tls' => __( 'TLS', 'post-smtp' ),
                     );
                     ?>
                     <td>
@@ -313,17 +321,17 @@ class PostmanConfigurationController {
                     </td>
                 </tr>
 
-				<tr>
-					<th scope="row"><?php _e('From Email', 'post-smtp' ); ?></th>
-					<td>
-						<input type="email" id="fallback-smtp-from-email"
-							   value="<?php echo $this->options->getFallbackFromEmail(); ?>"
-							   name="postman_options[<?php echo PostmanOptions::FALLBACK_FROM_EMAIL; ?>]"
-						>
-						<br>
-						<small><?php _e( "Use allowed email, for example: If you are using Gmail, type your Gmail adress.", 'post-smtp' ); ?></small>
-					</td>
-				</tr>
+                <tr>
+                    <th scope="row"><?php _e('From Email', 'post-smtp' ); ?></th>
+                    <td>
+                        <input type="email" id="fallback-smtp-from-email"
+                               value="<?php echo $this->options->getFallbackFromEmail(); ?>"
+                               name="postman_options[<?php echo PostmanOptions::FALLBACK_FROM_EMAIL; ?>]"
+                        >
+                        <br>
+                        <small><?php _e( "Use allowed email, for example: If you are using Gmail, type your Gmail adress.", 'post-smtp' ); ?></small>
+                    </td>
+                </tr>
 
                 <tr valign="">
                     <th scope="row"><?php _e( 'Use SMTP Authentication?', 'post-smtp' ); ?></th>
@@ -387,24 +395,7 @@ class PostmanConfigurationController {
 		do_settings_sections( PostmanAdminController::ADVANCED_OPTIONS );
 		print '</section>';
 
-		print '<section id="notifications">';
-		do_settings_sections( PostmanAdminController::NOTIFICATIONS_OPTIONS );
-
-        $currentKey = $this->options->getNotificationService();
-        $pushover = $currentKey == 'pushover' ? 'block' : 'none';
-        $slack = $currentKey == 'slack' ? 'block' : 'none';
-
-        echo '<div id="pushover_cred" style="display: ' . $pushover . ';">';
-        do_settings_sections( PostmanAdminController::NOTIFICATIONS_PUSHOVER_CRED );
-        echo '</div>';
-
-        echo '<div id="slack_cred" style="display: ' . $slack . ';">';
-        do_settings_sections( PostmanAdminController::NOTIFICATIONS_SLACK_CRED );
-        echo '</div>';
-
-        do_action( 'post_smtp_notification_settings' );
-
-		print '</section>';
+		do_action( 'post_smtp_settings_menu' );
 
 		submit_button();
 		print '</form>';
@@ -762,7 +753,7 @@ class PostmanManageConfigurationAjaxHandler extends PostmanAbstractAjaxHandler {
 			wp_send_json_success( $response );
 		} else {
 			/* translators: where %s is the URL to the Connectivity Test page */
-			$configuration ['message'] = sprintf( __( 'Postman can\'t find any way to send mail on your system. Run a <a href="%s">connectivity test</a>.', 'post-smtp' ), PostmanViewController::getPageUrl( PostmanViewController::PORT_TEST_SLUG ) );
+			$configuration ['message'] = sprintf( __( 'Postman can\'t find any way to send mail on your system. Run a <a href="%s">connectivity test</a>.', 'post-smtp' ), PostmanViewController::getPageUrl( PostmanConnectivityTestController::PORT_TEST_SLUG ) );
 			$response ['configuration'] = $configuration;
 			if ( $this->logger->isTrace() ) {
 				$this->logger->trace( 'configuration:' );
diff --git a/Postman/Postman-Configuration/PostmanRegisterConfigurationSettings.php b/Postman/Postman-Configuration/PostmanRegisterConfigurationSettings.php
index 705ef23..9c0170b 100644
--- a/Postman/Postman-Configuration/PostmanRegisterConfigurationSettings.php
+++ b/Postman/Postman-Configuration/PostmanRegisterConfigurationSettings.php
@@ -186,55 +186,7 @@ class PostmanSettingsRegistry {
 					'temporaryDirectoryCallback',
 			), PostmanAdminController::ADVANCED_OPTIONS, PostmanAdminController::ADVANCED_SECTION );
 
-			// Notifications
-			add_settings_section( PostmanAdminController::NOTIFICATIONS_SECTION, _x( 'Notifications Settings', 'Configuration Section Title', 'post-smtp' ), array(
-				$this,
-				'printNotificationsSectionInfo',
-			), PostmanAdminController::NOTIFICATIONS_OPTIONS );
-
-			add_settings_field( PostmanOptions::NOTIFICATION_SERVICE, _x( 'Notification Service', 'Configuration Input Field', 'post-smtp' ), array(
-				$this,
-				'notification_service_callback',
-			), PostmanAdminController::NOTIFICATIONS_OPTIONS, PostmanAdminController::NOTIFICATIONS_SECTION );
-
-			// Pushover
-			add_settings_section( 'pushover_credentials', _x( 'Pushover Credentials', 'Configuration Section Title', 'post-smtp' ), array(
-				$this,
-				'printNotificationsSectionInfo',
-			), PostmanAdminController::NOTIFICATIONS_PUSHOVER_CRED );
-
-			add_settings_field( PostmanOptions::PUSHOVER_USER, _x( 'Pushover User Key', 'Configuration Input Field', 'post-smtp' ), array(
-				$this,
-				'pushover_user_callback',
-			), PostmanAdminController::NOTIFICATIONS_PUSHOVER_CRED, 'pushover_credentials' );
-
-			add_settings_field( PostmanOptions::PUSHOVER_TOKEN, _x( 'Pushover App Token', 'Configuration Input Field', 'post-smtp' ), array(
-				$this,
-				'pushover_token_callback',
-			), PostmanAdminController::NOTIFICATIONS_PUSHOVER_CRED, 'pushover_credentials' );
-
-			// Slack
-			add_settings_section( 'slack_credentials', _x( 'Slack Credentials', 'Configuration Section Title', 'post-smtp' ), array(
-				$this,
-				'printNotificationsSectionInfo',
-			), PostmanAdminController::NOTIFICATIONS_SLACK_CRED );
-
-			add_settings_field( PostmanOptions::SLACK_TOKEN, _x( 'Slack Webhook', 'Configuration Input Field', 'post-smtp' ), array(
-				$this,
-				'slack_token_callback',
-			), PostmanAdminController::NOTIFICATIONS_SLACK_CRED, 'slack_credentials' );
-
-            add_settings_field( PostmanOptions::NOTIFICATION_USE_CHROME, _x( 'Push to chrome extension', 'Configuration Input Field', 'post-smtp' ), array(
-                $this,
-                'notification_use_chrome_callback',
-            ), PostmanAdminController::NOTIFICATIONS_OPTIONS, PostmanAdminController::NOTIFICATIONS_SECTION );
-
-            add_settings_field( PostmanOptions::NOTIFICATION_CHROME_UID, _x( 'Chrome Extension UID', 'Configuration Input Field', 'post-smtp' ), array(
-                $this,
-                'notification_chrome_uid_callback',
-            ), PostmanAdminController::NOTIFICATIONS_OPTIONS, PostmanAdminController::NOTIFICATIONS_SECTION );
-
-            do_action( 'post_smtp_settings' );
+            do_action( 'post_smtp_settings_fields' );
 		}
 	}
 
@@ -442,48 +394,6 @@ class PostmanSettingsRegistry {
 		printf( '</select><br/><span class="postman_input_description">%s</span>', $inputDescription );
 	}
 
-	public function notification_service_callback() {
-		$inputDescription =  __( 'Select the notification service you want to recieve alerts about failed emails.' );
-
-		$options = apply_filters('post_smtp_notification_service', array(
-            'default' => __( 'WP Admin Email', 'post-smtp' ),
-            'pushover' => __( 'Pushover', 'post-smtp' ),
-            'slack' => __( 'Slack', 'post-smtp' ),
-        ));
-
-		printf( '<select id="input_%2$s" class="input_%2$s" name="%1$s[%2$s]">', PostmanOptions::POSTMAN_OPTIONS, PostmanOptions::NOTIFICATION_SERVICE );
-		$currentKey = $this->options->getNotificationService();
-
-		foreach ( $options as $key => $label ) {
-            $this->printSelectOption( $label, $key, $currentKey );
-        }
-
-		printf( '</select><br/><span class="postman_input_description">%s</span>', $inputDescription );
-	}
-
-	public function notification_use_chrome_callback() {
-        $value = $this->options->useChromeExtension();
-        printf( '<input type="checkbox" id="input_%2$s" class="input_%2$s" name="%1$s[%2$s]" %3$s />', PostmanOptions::POSTMAN_OPTIONS, PostmanOptions::NOTIFICATION_USE_CHROME, $value ? 'checked="checked"' : '' );
-    }
-
-    public function notification_chrome_uid_callback() {
-        printf( '<input type="password" id="input_%2$s" class="input_%2$s" name="%1$s[%2$s]" value="%3$s" />', PostmanOptions::POSTMAN_OPTIONS, PostmanOptions::NOTIFICATION_CHROME_UID, PostmanUtils::obfuscatePassword( $this->options->getNotificationChromeUid() ) );
-    }
-
-	public function pushover_user_callback() {
-		printf( '<input type="password" id="pushover_user" name="%s[%s]" value="%s" />', PostmanOptions::POSTMAN_OPTIONS, PostmanOptions::PUSHOVER_USER, $this->options->getPushoverUser() );
-	}
-
-	public function pushover_token_callback() {
-		printf( '<input type="password" id="pushover_token" name="%s[%s]" value="%s" />', PostmanOptions::POSTMAN_OPTIONS, PostmanOptions::PUSHOVER_TOKEN, $this->options->getPushoverToken() );
-	}
-
-	public function slack_token_callback() {
-		printf( '<input type="password" id="slack_token" name="%s[%s]" value="%s" />', PostmanOptions::POSTMAN_OPTIONS, PostmanOptions::SLACK_TOKEN, $this->options->getSlackToken() );
-		echo '<a target="_blank" href="https://slack.postmansmtp.com/">' . __( 'Get your webhook URL here', 'post-smtp' ) . '</a>';
-
-	}
-
 	private function printSelectOption( $label, $optionKey, $currentKey ) {
 		$optionPattern = '<option value="%1$s" %2$s>%3$s</option>';
 		printf( $optionPattern, $optionKey, $optionKey == $currentKey ? 'selected="selected"' : '', $label );
diff --git a/Postman/Postman-Email-Log/PostmanEmailLogService.php b/Postman/Postman-Email-Log/PostmanEmailLogService.php
index 2538700..624342d 100644
--- a/Postman/Postman-Email-Log/PostmanEmailLogService.php
+++ b/Postman/Postman-Email-Log/PostmanEmailLogService.php
@@ -4,6 +4,7 @@ if ( ! defined( 'ABSPATH' ) ) {
 }
 
 require_once dirname(__DIR__ ) . '/PostmanLogFields.php';
+require_once POST_SMTP_PATH . '/Postman/Extensions/Core/Notifications/PostmanNotify.php';
 
 if ( ! class_exists( 'PostmanEmailLog' ) ) {
 	class PostmanEmailLog {
@@ -56,6 +57,9 @@ if ( ! class_exists( 'PostmanEmailLogService' ) ) {
 		 */
 		private function __construct() {
 			$this->logger = new PostmanLogger( get_class( $this ) );
+
+			add_action('post_smtp_on_success', array( $this, 'write_success_log' ), 10, 4 );
+			add_action('post_smtp_on_failed', array( $this, 'write_failed_log' ), 10, 5 );
 		}
 
 		/**
@@ -69,6 +73,20 @@ if ( ! class_exists( 'PostmanEmailLogService' ) ) {
 			return $inst;
 		}
 
+		public function write_success_log($log, $message, $transcript, $transport) {
+		    $options = PostmanOptions::getInstance();
+            if ( $options->getRunMode() == PostmanOptions::RUN_MODE_PRODUCTION || $options->getRunMode() == PostmanOptions::RUN_MODE_LOG_ONLY ) {
+                $this->writeSuccessLog( $log, $message, $transcript, $transport );
+            }
+        }
+
+        public function write_failed_log($log, $message, $transcript, $transport, $statusMessage) {
+            $options = PostmanOptions::getInstance();
+            if ( $options->getRunMode() == PostmanOptions::RUN_MODE_PRODUCTION || $options->getRunMode() == PostmanOptions::RUN_MODE_LOG_ONLY ) {
+                $this->writeFailureLog( $log, $message, $transcript, $transport, $statusMessage );
+            }
+        }
+
 		/**
 		 * Logs successful email attempts
 		 *
@@ -118,7 +136,6 @@ if ( ! class_exists( 'PostmanEmailLogService' ) ) {
 
 		    $options = PostmanOptions::getInstance();
 
-			$this->checkForLogErrors( $log ,$message );
             $new_status = $log->statusMessage;
 
 			if ( $options->is_fallback && empty( $log->statusMessage ) ) {
@@ -129,6 +146,8 @@ if ( ! class_exists( 'PostmanEmailLogService' ) ) {
                 $new_status = '( ** Fallback ** ) ' . $log->statusMessage;
             }
 
+            $new_status = apply_filters( 'post_smtp_log_status', $new_status, $log, $message );
+
 			// nothing here is sanitized as WordPress should take care of
 			// making database writes safe
 			$my_post = array(
@@ -187,50 +206,6 @@ if ( ! class_exists( 'PostmanEmailLogService' ) ) {
 			$purger->truncateLogItems( PostmanOptions::getInstance()->getMailLoggingMaxEntries() );
 		}
 
-		private function checkForLogErrors( PostmanEmailLog $log, $postMessage ) {
-			$message = __( 'You getting this message because an error detected while delivered your email.', 'post-smtp' );
-			$message .= "\r\n" . sprintf( __( 'For the domain: %1$s','post-smtp' ), get_bloginfo('url') );
-			$message .= "\r\n" . __( 'The log to paste when you open a support issue:', 'post-smtp' ) . "\r\n";
-
-			if ( $log->statusMessage && ! empty( $log->statusMessage ) ) {
-				require_once POST_SMTP_PATH . '/Postman/notifications/PostmanNotify.php';
-
-				$message = $message . $log->statusMessage;
-
-				$notification_service = PostmanOptions::getInstance()->getNotificationService();
-				switch ($notification_service) {
-					case 'default':
-						$notifyer = new PostmanMailNotify;
-						break;
-					case 'pushover':
-						$notifyer = new PostmanPushoverNotify;
-						break;
-					case 'slack':
-						$notifyer = new PostmanSlackNotify;
-						break;
-					default:
-						$notifyer = new PostmanMailNotify;
-				}
-
-				$notifyer = apply_filters( 'post_smtp_notifier', $notifyer, $notification_service );
-
-                // Notifications
-				$notify = new PostmanNotify( $notifyer );
-				$notify->send($message, $log);
-				$notify->push_to_chrome($log->statusMessage);
-			}
-
-			/**
-			 * @todo
-			 * After commented by me, check if it was needed.
-			 */
-			preg_match_all( '/(.*)From/s', $log->sessionTranscript, $matches );
-
-			if ( isset( $matches[1][0] ) && ! empty( $matches[1][0] ) && strpos( strtolower( $matches[1][0] ), 'error' ) !== false ) {
-				$message = $message . $log->sessionTranscript;
-			}
-		}
-
 		/**
 		 * Creates a Log object for use by writeToEmailLog()
 		 *
diff --git a/Postman/Postman-Mail/PostmanMessage.php b/Postman/Postman-Mail/PostmanMessage.php
index 12099ba..4099a02 100644
--- a/Postman/Postman-Mail/PostmanMessage.php
+++ b/Postman/Postman-Mail/PostmanMessage.php
@@ -450,7 +450,13 @@ if ( ! class_exists( 'PostmanMessage' ) ) {
 					break;
 				case 'reply-to' :
 					$this->logProcessHeader( 'Reply-To', $name, $content );
-					$this->setReplyTo( $content );
+                    $pattern = '/[a-z0-9_\-\+\.]+@[a-z0-9\-]+\.([a-z]{2,4})(?:\.[a-z]{2})?/i';
+                    preg_match_all($pattern, $content, $matches);
+
+                    if ( isset( $matches[0] ) && is_email( $matches[0] ) ) {
+                        $this->setReplyTo( $content );
+                    }
+
 					break;
 				case 'sender' :
 					$this->logProcessHeader( 'Sender', $name, $content );
diff --git a/Postman/Postman.php b/Postman/Postman.php
index 51b2ab1..9f4f7ce 100644
--- a/Postman/Postman.php
+++ b/Postman/Postman.php
@@ -70,6 +70,8 @@ class Postman {
 		require_once 'Postman-Mail/PostmanMyMailConnector.php';
 		require_once 'Postman-Mail/PostmanContactForm7.php';
 		require_once 'Phpmailer/PostsmtpMailer.php';
+        require_once 'Extensions/License/PostmanLicenseManager.php';
+        require_once 'Extensions/Admin/PostmanAdmin.php';
 		//require_once 'Postman-Mail/PostmanWooCommerce.php';
 
 		// get plugin metadata - alternative to get_plugin_data
@@ -188,6 +190,9 @@ class Postman {
 	 * ref: http://codex.wordpress.org/Plugin_API/Action_Reference#Actions_Run_During_a_Typical_Request
 	 */
 	public function on_plugins_loaded() {
+
+        PostmanLicenseManager::get_instance()->init();
+
 		// load the text domain
 		$this->loadTextDomain();
 
@@ -215,6 +220,7 @@ class Postman {
 	 * ref: https://codex.wordpress.org/Function_Reference/register_activation_hook
 	 */
 	public function on_activation() {
+
 		if ( $this->logger->isInfo() ) {
 			$this->logger->info( 'Activating plugin' );
 		}
diff --git a/Postman/PostmanAdminController.php b/Postman/PostmanAdminController.php
index 1e43c6d..128fa16 100644
--- a/Postman/PostmanAdminController.php
+++ b/Postman/PostmanAdminController.php
@@ -55,10 +55,6 @@ if ( ! class_exists( 'PostmanAdminController' ) ) {
 		const MULTISITE_SECTION = 'postman_multisite_section';
 		const ADVANCED_OPTIONS = 'postman_advanced_options';
 		const ADVANCED_SECTION = 'postman_advanced_section';
-		const NOTIFICATIONS_OPTIONS = 'postman_notifications_options';
-		const NOTIFICATIONS_SECTION = 'postman_notifications_section';
-		const NOTIFICATIONS_PUSHOVER_CRED = 'postman_pushover_cred';
-		const NOTIFICATIONS_SLACK_CRED = 'postman_slack_cred';
 		const EMAIL_VALIDATION_SECTION = 'postman_email_validation_section';
 		const EMAIL_VALIDATION_OPTIONS = 'postman_email_validation_options';
 
diff --git a/Postman/PostmanInputSanitizer.php b/Postman/PostmanInputSanitizer.php
index 75a0041..a8cdca7 100644
--- a/Postman/PostmanInputSanitizer.php
+++ b/Postman/PostmanInputSanitizer.php
@@ -73,16 +73,6 @@ if ( ! class_exists( 'PostmanInputSanitizer' ) ) {
 			$this->sanitizeInt( 'Transcript Size', PostmanOptions::TRANSCRIPT_SIZE, $input, $new_input );
 			$this->sanitizeString( 'Temporary Directory', PostmanOptions::TEMPORARY_DIRECTORY, $input, $new_input );
 
-			// Notifications
-			$this->sanitizeString( 'Pushover Service', PostmanOptions::NOTIFICATION_SERVICE, $input, $new_input, $this->options->getNotificationService() );
-			$this->sanitizePassword( 'Pushover Username', PostmanOptions::PUSHOVER_USER, $input, $new_input, $this->options->getPushoverUser() );
-			$this->sanitizePassword( 'Pushover Token', PostmanOptions::PUSHOVER_TOKEN, $input, $new_input, $this->options->getPushoverToken() );
-			$this->sanitizePassword( 'Slack Token', PostmanOptions::SLACK_TOKEN, $input, $new_input, $this->options->getSlackToken() );
-
-			// Chrome extension
-            $this->sanitizeString( 'Push Chrome Extension', PostmanOptions::NOTIFICATION_USE_CHROME, $input, $new_input );
-            $this->sanitizePassword( 'Push Chrome Extension UID', PostmanOptions::NOTIFICATION_CHROME_UID, $input, $new_input, $this->options->getNotificationChromeUid() );
-
             // Fallback
             $this->sanitizeString( 'Use fallback', PostmanOptions::FALLBACK_SMTP_ENABLED, $input, $new_input );
             $this->sanitizeString( 'Fallback hostname', PostmanOptions::FALLBACK_SMTP_HOSTNAME, $input, $new_input );
diff --git a/Postman/PostmanOptions.php b/Postman/PostmanOptions.php
index e2e78d6..30434bd 100644
--- a/Postman/PostmanOptions.php
+++ b/Postman/PostmanOptions.php
@@ -109,12 +109,6 @@ if ( ! class_exists( 'PostmanOptions' ) ) {
 		const TRANSCRIPT_SIZE = 'transcript_size';
 		const TEMPORARY_DIRECTORY = 'tmp_dir';
 		const DISABLE_EMAIL_VALIDAITON = 'disable_email_validation';
-		const NOTIFICATION_SERVICE = 'notification_service';
-        const NOTIFICATION_USE_CHROME = 'notification_use_chrome';
-        const NOTIFICATION_CHROME_UID = 'notification_chrome_uid';
-		const PUSHOVER_USER = 'pushover_user';
-		const PUSHOVER_TOKEN = 'pushover_token';
-		const SLACK_TOKEN = 'slack_token';
 
 		// Fallback
         const FALLBACK_SMTP_ENABLED = 'fallback_smtp_enabled';
@@ -133,7 +127,6 @@ if ( ! class_exists( 'PostmanOptions' ) ) {
 		const DEFAULT_MAIL_LOG_ENABLED = self::MAIL_LOG_ENABLED_OPTION_YES;
 		const DEFAULT_MAIL_LOG_ENTRIES = 250;
 		const DEFAULT_LOG_LEVEL = PostmanLogger::ERROR_INT;
-		const DEFAULT_NOTIFICATION_SERVICE = 'default';
 		const DEFAULT_TRANSPORT_TYPE = 'smtp'; // must match what's in PostmanSmtpModuleTransport
 		const DEFAULT_TCP_READ_TIMEOUT = 60;
 		const DEFAULT_TCP_CONNECTION_TIMEOUT = 10;
@@ -247,13 +240,6 @@ if ( ! class_exists( 'PostmanOptions' ) ) {
 			} else { 				return self::DEFAULT_LOG_LEVEL; }
 		}
 
-		public function getNotificationService() {
-			if ( isset( $this->options [ PostmanOptions::NOTIFICATION_SERVICE ] ) ) {
-				return $this->options [ PostmanOptions::NOTIFICATION_SERVICE ];
-			} else {
-				return self::DEFAULT_NOTIFICATION_SERVICE;
-			}
-		}
 
 		public function getForcedToRecipients() {
 			if ( isset( $this->options [ self::FORCED_TO_RECIPIENTS ] ) ) {
@@ -481,36 +467,6 @@ if ( ! class_exists( 'PostmanOptions' ) ) {
 				return $this->options [ PostmanOptions::MAILGUN_REGION ];
 			}
 		}
-
-		public function getPushoverUser() {
-			if ( isset( $this->options [ PostmanOptions::PUSHOVER_USER ] ) ) {
-				return base64_decode( $this->options [ PostmanOptions::PUSHOVER_USER ] );
-			}
-		}
-
-		public function getPushoverToken() {
-			if ( isset( $this->options [ PostmanOptions::PUSHOVER_TOKEN ] ) ) {
-				return base64_decode( $this->options [ PostmanOptions::PUSHOVER_TOKEN ] );
-			}
-		}
-
-		public function getSlackToken() {
-			if ( isset( $this->options [ PostmanOptions::SLACK_TOKEN ] ) ) {
-				return base64_decode( $this->options [ PostmanOptions::SLACK_TOKEN ] );
-			}
-		}
-
-        public function useChromeExtension() {
-            if ( isset( $this->options [ PostmanOptions::NOTIFICATION_USE_CHROME ] ) ) {
-                return $this->options [ PostmanOptions::NOTIFICATION_USE_CHROME ];
-            }
-        }
-
-        public function getNotificationChromeUid() {
-            if ( isset( $this->options [ PostmanOptions::NOTIFICATION_CHROME_UID ] ) ) {
-                return base64_decode( $this->options [ PostmanOptions::NOTIFICATION_CHROME_UID ] );
-            }
-        }
 		
 		public function getReplyTo() {
 			if ( isset( $this->options [ PostmanOptions::REPLY_TO ] ) ) {
diff --git a/Postman/PostmanWpMail.php b/Postman/PostmanWpMail.php
index 3a2b508..c108cce 100644
--- a/Postman/PostmanWpMail.php
+++ b/Postman/PostmanWpMail.php
@@ -25,6 +25,8 @@ if ( ! class_exists( 'PostmanWpMail' ) ) {
 			require_once 'Postman-Mail/PostmanMailEngine.php';
 			require_once 'Postman-Auth/PostmanAuthenticationManagerFactory.php';
 			require_once 'PostmanState.php';
+
+            PostmanEmailLogService::getInstance();
 		}
 
 		/**
@@ -95,7 +97,7 @@ if ( ! class_exists( 'PostmanWpMail' ) ) {
 		 * @param mixed $headers
 		 * @param mixed $attachments
 		 */
-		private function processWpMailCall( $to, $subject, $message, $headers, $attachments ) {
+		public function processWpMailCall( $to, $subject, $message, $headers, $attachments ) {
 			$this->logger->trace( 'wp_mail parameters before applying WordPress wp_mail filter:' );
 			$this->traceParameters( $to, $subject, $message, $headers, $attachments );
 
@@ -199,7 +201,6 @@ if ( ! class_exists( 'PostmanWpMail' ) ) {
 			// apply the WordPress filters
 			// may impact the from address, from email, charset and content-type
 			$message->applyFilters();
-			//do_action_ref_array( 'phpmailer_init', array( &$message ) );
 
 			// create the body parts (if they are both missing)
 			if ( $message->isBodyPartsEmpty() ) {
@@ -229,8 +230,13 @@ if ( ! class_exists( 'PostmanWpMail' ) ) {
 					}
 
 					$this->logger->debug( 'Sending mail' );
+
 					// may throw an exception attempting to contact the SMTP server
-					$engine->send( $message );
+                    if ( $send_email = apply_filters( 'post_smtp_do_send_email', true ) ) {
+                        $engine->send($message);
+                    } else {
+                        $this->transcript = 'Bypassed By MailControl For Post SMTP';
+                    }
 
 					// increment the success counter, unless we are just tesitng
 					if ( ! $testMode ) {
@@ -241,10 +247,10 @@ if ( ! class_exists( 'PostmanWpMail' ) ) {
 				// clean up
 				$this->postSend( $engine, $startTime, $options, $transport );
 
-				if ( $options->getRunMode() == PostmanOptions::RUN_MODE_PRODUCTION || $options->getRunMode() == PostmanOptions::RUN_MODE_LOG_ONLY ) {
-					// log the successful delivery
-					PostmanEmailLogService::getInstance()->writeSuccessLog( $log, $message, $engine->getTranscript(), $transport );
-				}
+                /**
+                 * Do stuff after successful delivery
+                 */
+                do_action( 'post_smtp_on_success', $log, $message, $engine->getTranscript(), $transport );
 
 				// return successful
 				return true;
@@ -263,10 +269,11 @@ if ( ! class_exists( 'PostmanWpMail' ) ) {
 				// clean up
 				$this->postSend( $engine, $startTime, $options, $transport );
 
-				if ( $options->getRunMode() == PostmanOptions::RUN_MODE_PRODUCTION || $options->getRunMode() == PostmanOptions::RUN_MODE_LOG_ONLY ) {
-					// log the failed delivery
-					PostmanEmailLogService::getInstance()->writeFailureLog( $log, $message, $engine->getTranscript(), $transport, $e->getMessage() );
-				}
+
+                /**
+                 * Do stuff after failed delivery
+                 */
+                do_action( 'post_smtp_on_failed', $log, $message, $engine->getTranscript(), $transport, $e->getMessage() );
 
                 // Fallback
                 if ( $this->fallback( $log, $message, $options ) ) {
diff --git a/Postman/notifications/INotify.php b/Postman/notifications/INotify.php
deleted file mode 100644
index f40548d..0000000
--- a/Postman/notifications/INotify.php
+++ /dev/null
@@ -1,7 +0,0 @@
-<?php
-if ( ! defined( 'ABSPATH' ) ) {
-    exit; // Exit if accessed directly
-}
-interface Postman_Notify {
-    public function send_message( $message );
-}
\ No newline at end of file
diff --git a/Postman/notifications/PostmanMailNotify.php b/Postman/notifications/PostmanMailNotify.php
deleted file mode 100644
index 922c304..0000000
--- a/Postman/notifications/PostmanMailNotify.php
+++ /dev/null
@@ -1,14 +0,0 @@
-<?php
-if ( ! defined( 'ABSPATH' ) ) {
-    exit; // Exit if accessed directly
-}
-class PostmanMailNotify implements Postman_Notify {
-
-    public function send_message($message)
-    {
-        $to_email = apply_filters( 'post_smtp_notify_email',get_bloginfo( 'admin_email' ) );
-        $domain = get_bloginfo( 'url' );
-
-        mail( $to_email, "{$domain}: " .  __( 'Post SMTP email error', 'post-smtp' ), $message , '', "-f{$to_email}" );
-    }
-}
\ No newline at end of file
diff --git a/Postman/notifications/PostmanNotify.php b/Postman/notifications/PostmanNotify.php
deleted file mode 100644
index 7654ecb..0000000
--- a/Postman/notifications/PostmanNotify.php
+++ /dev/null
@@ -1,42 +0,0 @@
-<?php
-if ( ! defined( 'ABSPATH' ) ) {
-    exit; // Exit if accessed directly
-}
-require_once 'INotify.php';
-require_once 'PostmanMailNotify.php';
-require_once 'PostmanPushoverNotify.php';
-require_once 'PostmanSlackNotify.php';
-
-class PostmanNotify {
-    private $notify;
-
-    public function __construct( Postman_Notify $notify ) {
-        $this->notify = $notify;
-    }
-
-    public function send( $message, $log ) {
-        $this->notify->send_message( $message );
-    }
-
-    public function push_to_chrome($message) {
-        $push_chrome = PostmanOptions::getInstance()->useChromeExtension();
-
-        if ( $push_chrome ) {
-            $uid = PostmanOptions::getInstance()->getNotificationChromeUid();
-
-            if ( empty( $uid ) ) {
-                return;
-            }
-
-            $url = 'https://postmansmtp.com/chrome/' . $uid;
-
-            $args = array(
-                'body' => array(
-                    'message' => $message
-                )
-            );
-
-            $response = wp_remote_post( $url , $args );
-        }
-    }
-}
\ No newline at end of file
diff --git a/Postman/notifications/PostmanPushoverNotify.php b/Postman/notifications/PostmanPushoverNotify.php
deleted file mode 100644
index 14ef7d2..0000000
--- a/Postman/notifications/PostmanPushoverNotify.php
+++ /dev/null
@@ -1,34 +0,0 @@
-<?php
-if ( ! defined( 'ABSPATH' ) ) {
-    exit; // Exit if accessed directly
-}
-class PostmanPushoverNotify implements Postman_Notify {
-
-    public function send_message($message)
-    {
-        $options = PostmanOptions::getInstance();
-
-        $api_url = "https://api.pushover.net/1/messages.json";
-        $app_token = $options->getPushoverToken();
-        $user_key = $options->getPushoverUser();
-
-        $args = array(
-            'body' => array(
-                "token" => $app_token,
-                "user" => $user_key,
-                "message" => $message,
-            )
-        );
-
-        $result = wp_remote_post( $api_url, $args );
-
-        if ( is_wp_error($result) ) {
-            error_log( __CLASS__ . ': ' . $result->get_error_message() );
-        }
-
-        $body = json_decode( wp_remote_retrieve_body( $result ), true );
-        if ( $body['status'] == 0 ) {
-            error_log( __CLASS__ . ': ' . print_r( $body, true ) );
-        }
-    }
-}
\ No newline at end of file
diff --git a/Postman/notifications/PostmanSlackNotify.php b/Postman/notifications/PostmanSlackNotify.php
deleted file mode 100644
index 5b6fae3..0000000
--- a/Postman/notifications/PostmanSlackNotify.php
+++ /dev/null
@@ -1,39 +0,0 @@
-<?php
-if ( ! defined( 'ABSPATH' ) ) {
-    exit; // Exit if accessed directly
-}
-class PostmanSlackNotify implements Postman_Notify {
-
-    public function send_message($message)
-    {
-        $options = PostmanOptions::getInstance();
-
-        $api_url = $options->getSlackToken();
-
-        $headers = array(
-            'content-type' => 'application/json'
-        );
-
-        $body = array(
-            'text' => $message
-        );
-
-        $args = array(
-            'headers' => $headers,
-            'body' => json_encode($body)
-        );
-
-        $result = wp_remote_post( $api_url, $args );
-
-        if ( is_wp_error($result) ) {
-            error_log( __CLASS__ . ': ' . $result->get_error_message() );
-        }
-
-        $code = wp_remote_retrieve_response_code( $result );
-        $message = wp_remote_retrieve_response_message( $result );
-
-        if ( $code != 200 && $message !== 'OK' ) {
-            error_log( __CLASS__ . ': ' . $message );
-        }
-    }
-}
\ No newline at end of file
-- 
cgit v1.2.3