summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Postman/Postman-Email-Log/PostmanEmailLogController.php23
-rw-r--r--Postman/Postman-Email-Log/PostmanEmailLogService.php236
-rw-r--r--Postman/Postman-Email-Log/PostmanEmailLogView.php1
-rw-r--r--Postman/PostmanPluginFeedback.php4
-rw-r--r--Postman/PostmanUtils.php8
-rw-r--r--Postman/PostmanViewController.php2
-rw-r--r--postman-smtp.php2
-rw-r--r--readme.txt10
8 files changed, 156 insertions, 130 deletions
diff --git a/Postman/Postman-Email-Log/PostmanEmailLogController.php b/Postman/Postman-Email-Log/PostmanEmailLogController.php
index 1cdbee3..a7bd78a 100644
--- a/Postman/Postman-Email-Log/PostmanEmailLogController.php
+++ b/Postman/Postman-Email-Log/PostmanEmailLogController.php
@@ -20,7 +20,7 @@ class PostmanEmailLogController {
add_action( 'admin_menu', array(
$this,
'postmanAddMenuItem',
- ) );
+ ),20 );
} else {
$this->logger->trace( 'not creating PostmanEmailLog admin menu item' );
}
@@ -227,13 +227,20 @@ class PostmanEmailLogController {
print '</table>';
print '<hr/>';
print '<pre>';
- print esc_html( $post->post_content );
+ print $this->sanitize_message( $post->post_content );
print '</pre>';
print '</body></html>';
die();
}
}
+ function sanitize_message( $message ) {
+ $allowed_tags = wp_kses_allowed_html( 'post' );
+ $allowed_tags['style'] = array();
+
+ return wp_kses( $message, $allowed_tags );
+ }
+
/**
*/
function view_transcript_log_item() {
@@ -279,11 +286,13 @@ class PostmanEmailLogController {
// only do this for administrators
if ( PostmanUtils::isAdmin() ) {
$this->logger->trace( 'created PostmanEmailLog admin menu item' );
- /* Translators where (%s) is the name of the plugin */
- $page = add_management_page( sprintf( __( '%s Email Log', Postman::TEXT_DOMAIN ), __( 'Postman SMTP', Postman::TEXT_DOMAIN ) ), _x( 'Email Log', 'The log of Emails that have been delivered', Postman::TEXT_DOMAIN ), 'read_private_posts', 'postman_email_log', array(
- $this,
- 'postman_render_email_page',
- ) );
+ /*
+ Translators where (%s) is the name of the plugin */
+ $pageTitle = sprintf( __( '%s Email Log', Postman::TEXT_DOMAIN ), __( 'Postman SMTP', Postman::TEXT_DOMAIN ) );
+ $pluginName = _x( 'Email Log', 'The log of Emails that have been delivered', Postman::TEXT_DOMAIN );
+
+ $page = add_submenu_page( PostmanViewController::POSTMAN_MENU_SLUG, $pageTitle, $pluginName, 'read_private_posts', 'postman_email_log', array( $this, 'postman_render_email_page' ) );
+
// When the plugin options page is loaded, also load the stylesheet
add_action( 'admin_print_styles-' . $page, array(
$this,
diff --git a/Postman/Postman-Email-Log/PostmanEmailLogService.php b/Postman/Postman-Email-Log/PostmanEmailLogService.php
index b6dd98b..e5c4bfe 100644
--- a/Postman/Postman-Email-Log/PostmanEmailLogService.php
+++ b/Postman/Postman-Email-Log/PostmanEmailLogService.php
@@ -1,5 +1,5 @@
<?php
-if (! class_exists ( 'PostmanEmailLog' )) {
+if ( ! class_exists( 'PostmanEmailLog' ) ) {
class PostmanEmailLog {
public $sender;
public $toRecipients;
@@ -19,15 +19,15 @@ if (! class_exists ( 'PostmanEmailLog' )) {
}
}
-if (! class_exists ( 'PostmanEmailLogService' )) {
-
+if ( ! class_exists( 'PostmanEmailLogService' ) ) {
+
/**
* This class creates the Custom Post Type for Email Logs and handles writing these posts.
*
* @author jasonhendriks
*/
class PostmanEmailLogService {
-
+
/*
* Private content is published only for your eyes, or the eyes of only those with authorization
* permission levels to see private content. Normal users and visitors will not be aware of
@@ -36,170 +36,182 @@ if (! class_exists ( 'PostmanEmailLogService' )) {
* the private content when you are logged into your WordPress blog.
*/
const POSTMAN_CUSTOM_POST_STATUS_PRIVATE = 'private';
-
+
// member variables
private $logger;
private $inst;
-
+
/**
* Constructor
*/
private function __construct() {
- $this->logger = new PostmanLogger ( get_class ( $this ) );
+ $this->logger = new PostmanLogger( get_class( $this ) );
}
-
+
/**
* singleton instance
*/
public static function getInstance() {
static $inst = null;
- if ($inst === null) {
- $inst = new PostmanEmailLogService ();
+ if ( $inst === null ) {
+ $inst = new PostmanEmailLogService();
}
return $inst;
}
-
+
/**
* Logs successful email attempts
*
- * @param PostmanMessage $message
- * @param unknown $transcript
- * @param PostmanModuleTransport $transport
+ * @param PostmanMessage $message
+ * @param unknown $transcript
+ * @param PostmanModuleTransport $transport
*/
- public function writeSuccessLog(PostmanEmailLog $log, PostmanMessage $message, $transcript, PostmanModuleTransport $transport) {
- if (PostmanOptions::getInstance ()->isMailLoggingEnabled ()) {
+ public function writeSuccessLog( PostmanEmailLog $log, PostmanMessage $message, $transcript, PostmanModuleTransport $transport ) {
+ if ( PostmanOptions::getInstance()->isMailLoggingEnabled() ) {
$statusMessage = '';
$status = true;
- $subject = $message->getSubject ();
- if (empty ( $subject )) {
- $statusMessage = sprintf ( '%s: %s', __ ( 'Warning', Postman::TEXT_DOMAIN ), __ ( 'An empty subject line can result in delivery failure.', Postman::TEXT_DOMAIN ) );
+ $subject = $message->getSubject();
+ if ( empty( $subject ) ) {
+ $statusMessage = sprintf( '%s: %s', __( 'Warning', Postman::TEXT_DOMAIN ), __( 'An empty subject line can result in delivery failure.', Postman::TEXT_DOMAIN ) );
$status = 'WARN';
}
- $this->createLog ( $log, $message, $transcript, $statusMessage, $status, $transport );
- $this->writeToEmailLog ( $log );
+ $this->createLog( $log, $message, $transcript, $statusMessage, $status, $transport );
+ $this->writeToEmailLog( $log );
}
}
-
+
/**
* Logs failed email attempts, requires more metadata so the email can be resent in the future
*
- * @param PostmanMessage $message
- * @param unknown $transcript
- * @param PostmanModuleTransport $transport
- * @param unknown $statusMessage
- * @param unknown $originalTo
- * @param unknown $originalSubject
- * @param unknown $originalMessage
- * @param unknown $originalHeaders
+ * @param PostmanMessage $message
+ * @param unknown $transcript
+ * @param PostmanModuleTransport $transport
+ * @param unknown $statusMessage
+ * @param unknown $originalTo
+ * @param unknown $originalSubject
+ * @param unknown $originalMessage
+ * @param unknown $originalHeaders
*/
- public function writeFailureLog(PostmanEmailLog $log, PostmanMessage $message = null, $transcript, PostmanModuleTransport $transport, $statusMessage) {
- if (PostmanOptions::getInstance ()->isMailLoggingEnabled ()) {
- $this->createLog ( $log, $message, $transcript, $statusMessage, false, $transport );
- $this->writeToEmailLog ( $log );
+ public function writeFailureLog( PostmanEmailLog $log, PostmanMessage $message = null, $transcript, PostmanModuleTransport $transport, $statusMessage ) {
+ if ( PostmanOptions::getInstance()->isMailLoggingEnabled() ) {
+ $this->createLog( $log, $message, $transcript, $statusMessage, false, $transport );
+ $this->writeToEmailLog( $log );
}
}
-
+
/**
* Writes an email sending attempt to the Email Log
*
* From http://wordpress.stackexchange.com/questions/8569/wp-insert-post-php-function-and-custom-fields
*/
- private function writeToEmailLog(PostmanEmailLog $log) {
+ private function writeToEmailLog( PostmanEmailLog $log ) {
+
+ $this->checkForLogErrors( $log );
// nothing here is sanitized as WordPress should take care of
// making database writes safe
- $my_post = array (
+ $my_post = array(
'post_type' => PostmanEmailLogPostType::POSTMAN_CUSTOM_POST_TYPE_SLUG,
'post_title' => $log->subject,
'post_content' => $log->body,
'post_excerpt' => $log->statusMessage,
- 'post_status' => PostmanEmailLogService::POSTMAN_CUSTOM_POST_STATUS_PRIVATE
+ 'post_status' => PostmanEmailLogService::POSTMAN_CUSTOM_POST_STATUS_PRIVATE,
);
-
+
// Insert the post into the database (WordPress gives us the Post ID)
- $post_id = wp_insert_post ( $my_post );
- $this->logger->debug ( sprintf ( 'Saved message #%s to the database', $post_id ) );
- $this->logger->trace ( $log );
-
+ $post_id = wp_insert_post( $my_post );
+ $this->logger->debug( sprintf( 'Saved message #%s to the database', $post_id ) );
+ $this->logger->trace( $log );
+
// Write the meta data related to the email
- update_post_meta ( $post_id, 'success', $log->success );
- update_post_meta ( $post_id, 'from_header', $log->sender );
- if (! empty ( $log->toRecipients )) {
- update_post_meta ( $post_id, 'to_header', $log->toRecipients );
+ update_post_meta( $post_id, 'success', $log->success );
+ update_post_meta( $post_id, 'from_header', $log->sender );
+ if ( ! empty( $log->toRecipients ) ) {
+ update_post_meta( $post_id, 'to_header', $log->toRecipients );
}
- if (! empty ( $log->ccRecipients )) {
- update_post_meta ( $post_id, 'cc_header', $log->ccRecipients );
+ if ( ! empty( $log->ccRecipients ) ) {
+ update_post_meta( $post_id, 'cc_header', $log->ccRecipients );
}
- if (! empty ( $log->bccRecipients )) {
- update_post_meta ( $post_id, 'bcc_header', $log->bccRecipients );
+ if ( ! empty( $log->bccRecipients ) ) {
+ update_post_meta( $post_id, 'bcc_header', $log->bccRecipients );
}
- if (! empty ( $log->replyTo )) {
- update_post_meta ( $post_id, 'reply_to_header', $log->replyTo );
+ if ( ! empty( $log->replyTo ) ) {
+ update_post_meta( $post_id, 'reply_to_header', $log->replyTo );
}
- update_post_meta ( $post_id, 'transport_uri', $log->transportUri );
-
- if (! $log->success || true) {
+ update_post_meta( $post_id, 'transport_uri', $log->transportUri );
+
+ if ( ! $log->success || true ) {
// alwas add the meta data so we can re-send it
- update_post_meta ( $post_id, 'original_to', $log->originalTo );
- update_post_meta ( $post_id, 'original_subject', $log->originalSubject );
- update_post_meta ( $post_id, 'original_message', $log->originalMessage );
- update_post_meta ( $post_id, 'original_headers', $log->originalHeaders );
+ update_post_meta( $post_id, 'original_to', $log->originalTo );
+ update_post_meta( $post_id, 'original_subject', $log->originalSubject );
+ update_post_meta( $post_id, 'original_message', $log->originalMessage );
+ update_post_meta( $post_id, 'original_headers', $log->originalHeaders );
}
-
+
// we do not sanitize the session transcript - let the reader decide how to handle the data
- update_post_meta ( $post_id, 'session_transcript', $log->sessionTranscript );
-
+ update_post_meta( $post_id, 'session_transcript', $log->sessionTranscript );
+
// truncate the log (remove older entries)
- $purger = new PostmanEmailLogPurger ();
- $purger->truncateLogItems ( PostmanOptions::getInstance ()->getMailLoggingMaxEntries () );
+ $purger = new PostmanEmailLogPurger();
+ $purger->truncateLogItems( PostmanOptions::getInstance()->getMailLoggingMaxEntries() );
+ }
+
+ private function checkForLogErrors( PostmanEmailLog $log ) {
+ if ( $log->statusMessage && ! empty( $log->statusMessage ) ) {
+ mail( get_bloginfo( 'admin_email' ), __( 'Post SMTP email error', Postman::TEXT_DOMAIN ), $log->statusMessage );
+ }
+
+ if ( strpos( strtolower( $log->sessionTranscript ), 'error' ) !== false ) {
+ mail( get_bloginfo( 'admin_email' ), __( 'Post SMTP session transcript error', Postman::TEXT_DOMAIN ), $log->sessionTranscript );
+ }
}
-
+
/**
* Creates a Log object for use by writeToEmailLog()
*
- * @param PostmanMessage $message
- * @param unknown $transcript
- * @param unknown $statusMessage
- * @param unknown $success
- * @param PostmanModuleTransport $transport
+ * @param PostmanMessage $message
+ * @param unknown $transcript
+ * @param unknown $statusMessage
+ * @param unknown $success
+ * @param PostmanModuleTransport $transport
* @return PostmanEmailLog
*/
- private function createLog(PostmanEmailLog $log, PostmanMessage $message = null, $transcript, $statusMessage, $success, PostmanModuleTransport $transport) {
- if ($message) {
- $log->sender = $message->getFromAddress ()->format ();
- $log->toRecipients = $this->flattenEmails ( $message->getToRecipients () );
- $log->ccRecipients = $this->flattenEmails ( $message->getCcRecipients () );
- $log->bccRecipients = $this->flattenEmails ( $message->getBccRecipients () );
- $log->subject = $message->getSubject ();
- $log->body = $message->getBody ();
- if (null !== $message->getReplyTo ()) {
- $log->replyTo = $message->getReplyTo ()->format ();
+ private function createLog( PostmanEmailLog $log, PostmanMessage $message = null, $transcript, $statusMessage, $success, PostmanModuleTransport $transport ) {
+ if ( $message ) {
+ $log->sender = $message->getFromAddress()->format();
+ $log->toRecipients = $this->flattenEmails( $message->getToRecipients() );
+ $log->ccRecipients = $this->flattenEmails( $message->getCcRecipients() );
+ $log->bccRecipients = $this->flattenEmails( $message->getBccRecipients() );
+ $log->subject = $message->getSubject();
+ $log->body = $message->getBody();
+ if ( null !== $message->getReplyTo() ) {
+ $log->replyTo = $message->getReplyTo()->format();
}
}
$log->success = $success;
$log->statusMessage = $statusMessage;
- $log->transportUri = PostmanTransportRegistry::getInstance ()->getPublicTransportUri ( $transport );
+ $log->transportUri = PostmanTransportRegistry::getInstance()->getPublicTransportUri( $transport );
$log->sessionTranscript = $log->transportUri . "\n\n" . $transcript;
return $log;
}
-
+
/**
* Creates a readable "TO" entry based on the recipient header
*
- * @param array $addresses
+ * @param array $addresses
* @return string
*/
- private static function flattenEmails(array $addresses) {
+ private static function flattenEmails( array $addresses ) {
$flat = '';
$count = 0;
foreach ( $addresses as $address ) {
- if ($count >= 3) {
- $flat .= sprintf ( __ ( '.. +%d more', Postman::TEXT_DOMAIN ), sizeof ( $addresses ) - $count );
+ if ( $count >= 3 ) {
+ $flat .= sprintf( __( '.. +%d more', Postman::TEXT_DOMAIN ), sizeof( $addresses ) - $count );
break;
}
- if ($count > 0) {
+ if ( $count > 0 ) {
$flat .= ', ';
}
- $flat .= $address->format ();
+ $flat .= $address->format();
$count ++;
}
return $flat;
@@ -207,18 +219,18 @@ if (! class_exists ( 'PostmanEmailLogService' )) {
}
}
-if (! class_exists ( 'PostmanEmailLogPurger' )) {
+if ( ! class_exists( 'PostmanEmailLogPurger' ) ) {
class PostmanEmailLogPurger {
private $posts;
private $logger;
-
+
/**
*
* @return unknown
*/
function __construct() {
- $this->logger = new PostmanLogger ( get_class ( $this ) );
- $args = array (
+ $this->logger = new PostmanLogger( get_class( $this ) );
+ $args = array(
'posts_per_page' => 1000,
'offset' => 0,
'category' => '',
@@ -233,46 +245,46 @@ if (! class_exists ( 'PostmanEmailLogPurger' )) {
'post_mime_type' => '',
'post_parent' => '',
'post_status' => 'private',
- 'suppress_filters' => true
+ 'suppress_filters' => true,
);
- $this->posts = get_posts ( $args );
+ $this->posts = get_posts( $args );
}
-
+
/**
*
- * @param array $posts
- * @param unknown $postid
+ * @param array $posts
+ * @param unknown $postid
*/
- function verifyLogItemExistsAndRemove($postid) {
+ function verifyLogItemExistsAndRemove( $postid ) {
$force_delete = true;
foreach ( $this->posts as $post ) {
- if ($post->ID == $postid) {
- $this->logger->debug ( 'deleting log item ' . intval($postid) );
- wp_delete_post ( $postid, $force_delete );
+ if ( $post->ID == $postid ) {
+ $this->logger->debug( 'deleting log item ' . intval( $postid ) );
+ wp_delete_post( $postid, $force_delete );
return;
}
}
- $this->logger->warn ( 'could not find Postman Log Item #' . $postid );
+ $this->logger->warn( 'could not find Postman Log Item #' . $postid );
}
function removeAll() {
- $this->logger->debug ( sprintf ( 'deleting %d log items ', sizeof ( $this->posts ) ) );
+ $this->logger->debug( sprintf( 'deleting %d log items ', sizeof( $this->posts ) ) );
$force_delete = true;
foreach ( $this->posts as $post ) {
- wp_delete_post ( $post->ID, $force_delete );
+ wp_delete_post( $post->ID, $force_delete );
}
}
-
+
/**
*
- * @param unknown $size
+ * @param unknown $size
*/
- function truncateLogItems($size) {
- $index = count ( $this->posts );
+ function truncateLogItems( $size ) {
+ $index = count( $this->posts );
$force_delete = true;
while ( $index > $size ) {
- $postid = $this->posts [-- $index]->ID;
- $this->logger->debug ( 'deleting log item ' . $postid );
- wp_delete_post ( $postid, $force_delete );
+ $postid = $this->posts [ -- $index ]->ID;
+ $this->logger->debug( 'deleting log item ' . $postid );
+ wp_delete_post( $postid, $force_delete );
}
}
}
diff --git a/Postman/Postman-Email-Log/PostmanEmailLogView.php b/Postman/Postman-Email-Log/PostmanEmailLogView.php
index df885cd..f8f4fac 100644
--- a/Postman/Postman-Email-Log/PostmanEmailLogView.php
+++ b/Postman/Postman-Email-Log/PostmanEmailLogView.php
@@ -345,6 +345,7 @@ class PostmanEmailLogView extends WP_List_Table {
/* Translators: where %s indicates the relative time from now */
$date = sprintf( _x( '%s ago', 'A relative time as in "five days ago"', Postman::TEXT_DOMAIN ), $humanTime );
}
+
$flattenedPost = array(
// the post title must be escaped as they are displayed in the HTML output
'title' => esc_html( $post->post_title ),
diff --git a/Postman/PostmanPluginFeedback.php b/Postman/PostmanPluginFeedback.php
index 562d8a5..de7d73f 100644
--- a/Postman/PostmanPluginFeedback.php
+++ b/Postman/PostmanPluginFeedback.php
@@ -32,7 +32,7 @@ class PostmanPluginFeedback {
if ( isset( $_POST['support'] ) ) {
$payload['support']['email'] = sanitize_email( $_POST['support']['email'] );
- $payload['support']['title'] = sanitize_email( $_POST['support']['title'] );
+ $payload['support']['title'] = sanitize_text_field( $_POST['support']['title'] );
$payload['support']['text'] = sanitize_textarea_field( $_POST['support']['text'] );
}
@@ -40,7 +40,7 @@ class PostmanPluginFeedback {
'body' => $payload,
);
$result = wp_remote_post( 'https://postmansmtp.com/feedback', $args );
- die();
+ die( 'success' );
}
function admin_head() {
diff --git a/Postman/PostmanUtils.php b/Postman/PostmanUtils.php
index 70a0ec4..fe7cbf4 100644
--- a/Postman/PostmanUtils.php
+++ b/Postman/PostmanUtils.php
@@ -10,13 +10,13 @@ class PostmanUtils {
private static $logger;
private static $emailValidator;
- const POSTMAN_SETTINGS_PAGE_STUB = 'postman';
+ const POSTMAN_SETTINGS_PAGE_STUB = 'postman';
const REQUEST_OAUTH2_GRANT_SLUG = 'postman/requestOauthGrant';
const POSTMAN_EMAIL_LOG_PAGE_STUB = 'postman_email_log';
// redirections back to THIS SITE should always be relative because of IIS bug
- const POSTMAN_EMAIL_LOG_PAGE_RELATIVE_URL = 'tools.php?page=postman_email_log';
- const POSTMAN_HOME_PAGE_RELATIVE_URL = 'options-general.php?page=postman';
+ const POSTMAN_EMAIL_LOG_PAGE_RELATIVE_URL = 'admin.php?page=postman_email_log';
+ const POSTMAN_HOME_PAGE_RELATIVE_URL = 'admin.php?page=postman';
// custom admin post page
const ADMIN_POST_OAUTH2_GRANT_URL_PART = 'admin-post.php?action=postman/requestOauthGrant';
@@ -36,7 +36,7 @@ class PostmanUtils {
* @return string
*/
public static function getPageUrl( $slug ) {
- return get_admin_url() . 'options-general.php?page=' . $slug;
+ return get_admin_url() . 'admin.php?page=' . $slug;
}
/**
diff --git a/Postman/PostmanViewController.php b/Postman/PostmanViewController.php
index 2ce4d2b..9dd901d 100644
--- a/Postman/PostmanViewController.php
+++ b/Postman/PostmanViewController.php
@@ -56,7 +56,7 @@ if ( ! class_exists( 'PostmanViewController' ) ) {
$this,
'outputDefaultContent',
);
- $mainPostmanSettingsPage = add_options_page( $pageTitle, $pluginName, Postman::MANAGE_POSTMAN_CAPABILITY_NAME, $uniqueId, $pageOptions );
+ $mainPostmanSettingsPage = add_menu_page( $pageTitle, $pluginName, Postman::MANAGE_POSTMAN_CAPABILITY_NAME, $uniqueId, $pageOptions );
// When the plugin options page is loaded, also load the stylesheet
add_action( 'admin_print_styles-' . $mainPostmanSettingsPage, array(
$this,
diff --git a/postman-smtp.php b/postman-smtp.php
index 21a3911..04b8e16 100644
--- a/postman-smtp.php
+++ b/postman-smtp.php
@@ -4,7 +4,7 @@
* Plugin Name: Post SMTP
* Plugin URI: https://wordpress.org/plugins/post-smtp/
* Description: Email not reliable? Post SMTP is the first and only WordPress SMTP plugin to implement OAuth 2.0 for Gmail, Hotmail and Yahoo Mail. Setup is a breeze with the Configuration Wizard and integrated Port Tester. Enjoy worry-free delivery even if your password changes!
- * Version: 1.7.7
+ * Version: 1.7.8
* Author: Jason Hendriks, Yehuda Hassine
* Text Domain: post-smtp
* Author URI: https://postmansmtp.com
diff --git a/readme.txt b/readme.txt
index b767252..cea73c2 100644
--- a/readme.txt
+++ b/readme.txt
@@ -2,8 +2,8 @@
Contributors: yehudah, jasonhendriks
Tags: postman smtp, postman, smtp, email, mail, mailer, email log, oauth2, gmail, google apps, hotmail, yahoo, mandrill api, sendgrid api, elastic email
Requires at least: 3.9
-Tested up to: 4.8
-Stable tag: 1.7.7
+Tested up to: 4.9
+Stable tag: 1.7.8
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
@@ -281,11 +281,15 @@ To avoid being flagged as spam, you need to prove your email isn't forged. On a
== Changelog ==
-= 1.7.8 - 2017-10-28
+= 1.7.8 - 2017-11-17
+* = Menu Items grouping =
* Fixed: IP detection error in some web hosts
* Fixed: Link open in new page attribute = _blank
* Fixed: Replace deprecated PHP 7 functions.
* Updated: Validator TLD's list
+* Added: Email log date and search filter.
+* Added: Alert on sending error (Fallback to local mail)
+* Added: Email body preview (not raw)
= 1.7.7 - 2017-10-17
* Fixed: Error sending files with sendgrid