Improve email sending system and add Postmark as mail transport
This commit is contained in:
@@ -112,7 +112,7 @@ class Webhook extends Base
|
|||||||
$this->response->text('Not Authorized', 401);
|
$this->response->text('Not Authorized', 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
echo $this->postmarkWebhook->parsePayload($this->request->getJson() ?: array()) ? 'PARSED' : 'IGNORED';
|
echo $this->postmark->receiveEmail($this->request->getJson() ?: array()) ? 'PARSED' : 'IGNORED';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use Pimple\Container;
|
|||||||
* @author Frederic Guillot
|
* @author Frederic Guillot
|
||||||
*
|
*
|
||||||
* @property \Core\Helper $helper
|
* @property \Core\Helper $helper
|
||||||
|
* @property \Core\EmailClient $emailClient
|
||||||
* @property \Core\HttpClient $httpClient
|
* @property \Core\HttpClient $httpClient
|
||||||
* @property \Core\Paginator $paginator
|
* @property \Core\Paginator $paginator
|
||||||
* @property \Core\Request $request
|
* @property \Core\Request $request
|
||||||
@@ -22,9 +23,10 @@ use Pimple\Container;
|
|||||||
* @property \Integration\HipchatWebhook $hipchatWebhook
|
* @property \Integration\HipchatWebhook $hipchatWebhook
|
||||||
* @property \Integration\Jabber $jabber
|
* @property \Integration\Jabber $jabber
|
||||||
* @property \Integration\MailgunWebhook $mailgunWebhook
|
* @property \Integration\MailgunWebhook $mailgunWebhook
|
||||||
* @property \Integration\PostmarkWebhook $postmarkWebhook
|
* @property \Integration\Postmark $postmark
|
||||||
* @property \Integration\SendgridWebhook $sendgridWebhook
|
* @property \Integration\SendgridWebhook $sendgridWebhook
|
||||||
* @property \Integration\SlackWebhook $slackWebhook
|
* @property \Integration\SlackWebhook $slackWebhook
|
||||||
|
* @property \Integration\Smtp $smtp
|
||||||
* @property \Model\Acl $acl
|
* @property \Model\Acl $acl
|
||||||
* @property \Model\Action $action
|
* @property \Model\Action $action
|
||||||
* @property \Model\Authentication $authentication
|
* @property \Model\Authentication $authentication
|
||||||
|
|||||||
43
app/Core/EmailClient.php
Normal file
43
app/Core/EmailClient.php
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Core;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mail client
|
||||||
|
*
|
||||||
|
* @package core
|
||||||
|
* @author Frederic Guillot
|
||||||
|
*/
|
||||||
|
class EmailClient extends Base
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Send a HTML email
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param string $email
|
||||||
|
* @param string $name
|
||||||
|
* @param string $subject
|
||||||
|
* @param string $html
|
||||||
|
*/
|
||||||
|
public function send($email, $name, $subject, $html)
|
||||||
|
{
|
||||||
|
$this->container['logger']->debug('Sending email to '.$email.' ('.MAIL_TRANSPORT.')');
|
||||||
|
|
||||||
|
$start_time = microtime(true);
|
||||||
|
$author = 'Kanboard';
|
||||||
|
|
||||||
|
if (Session::isOpen() && $this->userSession->isLogged()) {
|
||||||
|
$author = e('%s via Kanboard', $this->user->getFullname($this->session['user']));
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (MAIL_TRANSPORT) {
|
||||||
|
case 'postmark':
|
||||||
|
$this->postmark->sendEmail($email, $name, $subject, $html, $author);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$this->smtp->sendEmail($email, $name, $subject, $html, $author);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->container['logger']->debug('Email sent in '.round(microtime(true) - $start_time, 6).' seconds');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,22 +2,20 @@
|
|||||||
|
|
||||||
namespace Core;
|
namespace Core;
|
||||||
|
|
||||||
use Pimple\Container;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP client
|
* HTTP client
|
||||||
*
|
*
|
||||||
* @package core
|
* @package core
|
||||||
* @author Frederic Guillot
|
* @author Frederic Guillot
|
||||||
*/
|
*/
|
||||||
class HttpClient
|
class HttpClient extends Base
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* HTTP connection timeout in seconds
|
* HTTP connection timeout in seconds
|
||||||
*
|
*
|
||||||
* @var integer
|
* @var integer
|
||||||
*/
|
*/
|
||||||
const HTTP_TIMEOUT = 2;
|
const HTTP_TIMEOUT = 5;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of maximum redirections for the HTTP client
|
* Number of maximum redirections for the HTTP client
|
||||||
@@ -31,26 +29,7 @@ class HttpClient
|
|||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
const HTTP_USER_AGENT = 'Kanboard Webhook';
|
const HTTP_USER_AGENT = 'Kanboard';
|
||||||
|
|
||||||
/**
|
|
||||||
* Container instance
|
|
||||||
*
|
|
||||||
* @access protected
|
|
||||||
* @var \Pimple\Container
|
|
||||||
*/
|
|
||||||
protected $container;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*
|
|
||||||
* @access public
|
|
||||||
* @param \Pimple\Container $container
|
|
||||||
*/
|
|
||||||
public function __construct(Container $container)
|
|
||||||
{
|
|
||||||
$this->container = $container;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a POST HTTP request
|
* Send a POST HTTP request
|
||||||
@@ -58,19 +37,20 @@ class HttpClient
|
|||||||
* @access public
|
* @access public
|
||||||
* @param string $url
|
* @param string $url
|
||||||
* @param array $data
|
* @param array $data
|
||||||
|
* @param array $headers
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function post($url, array $data)
|
public function post($url, array $data, array $headers = array())
|
||||||
{
|
{
|
||||||
if (empty($url)) {
|
if (empty($url)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
$headers = array(
|
$headers = array_merge(array(
|
||||||
'User-Agent: '.self::HTTP_USER_AGENT,
|
'User-Agent: '.self::HTTP_USER_AGENT,
|
||||||
'Content-Type: application/json',
|
'Content-Type: application/json',
|
||||||
'Connection: close',
|
'Connection: close',
|
||||||
);
|
), $headers);
|
||||||
|
|
||||||
$context = stream_context_create(array(
|
$context = stream_context_create(array(
|
||||||
'http' => array(
|
'http' => array(
|
||||||
@@ -83,12 +63,21 @@ class HttpClient
|
|||||||
)
|
)
|
||||||
));
|
));
|
||||||
|
|
||||||
$response = @file_get_contents(trim($url), false, $context);
|
$stream = @fopen(trim($url), 'r', false, $context);
|
||||||
|
$response = '';
|
||||||
|
|
||||||
|
if (is_resource($stream)) {
|
||||||
|
$response = stream_get_contents($stream);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->container['logger']->error('HttpClient: request failed');
|
||||||
|
}
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
$this->container['logger']->debug($url);
|
$this->container['logger']->debug('HttpClient: url='.$url);
|
||||||
$this->container['logger']->debug(var_export($data, true));
|
$this->container['logger']->debug('HttpClient: payload='.var_export($data, true));
|
||||||
$this->container['logger']->debug($response);
|
$this->container['logger']->debug('HttpClient: metadata='.var_export(@stream_get_meta_data($stream), true));
|
||||||
|
$this->container['logger']->debug('HttpClient: response='.$response);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $response;
|
return $response;
|
||||||
|
|||||||
@@ -5,13 +5,40 @@ namespace Integration;
|
|||||||
use HTML_To_Markdown;
|
use HTML_To_Markdown;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Postmark Webhook
|
* Postmark integration
|
||||||
*
|
*
|
||||||
* @package integration
|
* @package integration
|
||||||
* @author Frederic Guillot
|
* @author Frederic Guillot
|
||||||
*/
|
*/
|
||||||
class PostmarkWebhook extends \Core\Base
|
class Postmark extends \Core\Base
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Send a HTML email
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param string $email
|
||||||
|
* @param string $name
|
||||||
|
* @param string $subject
|
||||||
|
* @param string $html
|
||||||
|
* @param string $author
|
||||||
|
*/
|
||||||
|
public function sendEmail($email, $name, $subject, $html, $author)
|
||||||
|
{
|
||||||
|
$headers = array(
|
||||||
|
'Accept: application/json',
|
||||||
|
'X-Postmark-Server-Token: '.POSTMARK_API_TOKEN,
|
||||||
|
);
|
||||||
|
|
||||||
|
$payload = array(
|
||||||
|
'From' => sprintf('%s <%s>', $author, MAIL_FROM),
|
||||||
|
'To' => sprintf('%s <%s>', $name, $email),
|
||||||
|
'Subject' => $subject,
|
||||||
|
'HtmlBody' => $html,
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->httpClient->post('https://api.postmarkapp.com/email', $payload, $headers);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse incoming email
|
* Parse incoming email
|
||||||
*
|
*
|
||||||
@@ -19,7 +46,7 @@ class PostmarkWebhook extends \Core\Base
|
|||||||
* @param array $payload Incoming email
|
* @param array $payload Incoming email
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public function parsePayload(array $payload)
|
public function receiveEmail(array $payload)
|
||||||
{
|
{
|
||||||
if (empty($payload['From']) || empty($payload['Subject']) || empty($payload['MailboxHash'])) {
|
if (empty($payload['From']) || empty($payload['Subject']) || empty($payload['MailboxHash'])) {
|
||||||
return false;
|
return false;
|
||||||
@@ -29,7 +56,7 @@ class PostmarkWebhook extends \Core\Base
|
|||||||
$user = $this->user->getByEmail($payload['From']);
|
$user = $this->user->getByEmail($payload['From']);
|
||||||
|
|
||||||
if (empty($user)) {
|
if (empty($user)) {
|
||||||
$this->container['logger']->debug('PostmarkWebhook: ignored => user not found');
|
$this->container['logger']->debug('Postmark: ignored => user not found');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,13 +64,13 @@ class PostmarkWebhook extends \Core\Base
|
|||||||
$project = $this->project->getByIdentifier($payload['MailboxHash']);
|
$project = $this->project->getByIdentifier($payload['MailboxHash']);
|
||||||
|
|
||||||
if (empty($project)) {
|
if (empty($project)) {
|
||||||
$this->container['logger']->debug('PostmarkWebhook: ignored => project not found');
|
$this->container['logger']->debug('Postmark: ignored => project not found');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The user must be member of the project
|
// The user must be member of the project
|
||||||
if (! $this->projectPermission->isMember($project['id'], $user['id'])) {
|
if (! $this->projectPermission->isMember($project['id'], $user['id'])) {
|
||||||
$this->container['logger']->debug('PostmarkWebhook: ignored => user is not member of the project');
|
$this->container['logger']->debug('Postmark: ignored => user is not member of the project');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
71
app/Integration/Smtp.php
Normal file
71
app/Integration/Smtp.php
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Integration;
|
||||||
|
|
||||||
|
use Swift_Message;
|
||||||
|
use Swift_Mailer;
|
||||||
|
use Swift_MailTransport;
|
||||||
|
use Swift_SendmailTransport;
|
||||||
|
use Swift_SmtpTransport;
|
||||||
|
use Swift_TransportException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Smtp
|
||||||
|
*
|
||||||
|
* @package integration
|
||||||
|
* @author Frederic Guillot
|
||||||
|
*/
|
||||||
|
class Smtp extends \Core\Base
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Send a HTML email
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param string $email
|
||||||
|
* @param string $name
|
||||||
|
* @param string $subject
|
||||||
|
* @param string $html
|
||||||
|
* @param string $author
|
||||||
|
*/
|
||||||
|
public function sendEmail($email, $name, $subject, $html, $author)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
|
||||||
|
$message = Swift_Message::newInstance()
|
||||||
|
->setSubject($subject)
|
||||||
|
->setFrom(array(MAIL_FROM => $author))
|
||||||
|
->setBody($html, 'text/html')
|
||||||
|
->setTo(array($email => $name));
|
||||||
|
|
||||||
|
Swift_Mailer::newInstance($this->getTransport())->send($message);
|
||||||
|
}
|
||||||
|
catch (Swift_TransportException $e) {
|
||||||
|
$this->container['logger']->error($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get SwiftMailer transport
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
* @return \Swift_Transport
|
||||||
|
*/
|
||||||
|
private function getTransport()
|
||||||
|
{
|
||||||
|
switch (MAIL_TRANSPORT) {
|
||||||
|
case 'smtp':
|
||||||
|
$transport = Swift_SmtpTransport::newInstance(MAIL_SMTP_HOSTNAME, MAIL_SMTP_PORT);
|
||||||
|
$transport->setUsername(MAIL_SMTP_USERNAME);
|
||||||
|
$transport->setPassword(MAIL_SMTP_PASSWORD);
|
||||||
|
$transport->setEncryption(MAIL_SMTP_ENCRYPTION);
|
||||||
|
break;
|
||||||
|
case 'sendmail':
|
||||||
|
$transport = Swift_SendmailTransport::newInstance(MAIL_SENDMAIL_COMMAND);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$transport = Swift_MailTransport::newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $transport;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,9 +4,6 @@ namespace Model;
|
|||||||
|
|
||||||
use Core\Session;
|
use Core\Session;
|
||||||
use Core\Translator;
|
use Core\Translator;
|
||||||
use Swift_Message;
|
|
||||||
use Swift_Mailer;
|
|
||||||
use Swift_TransportException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notification model
|
* Notification model
|
||||||
@@ -101,43 +98,22 @@ class Notification extends Base
|
|||||||
*/
|
*/
|
||||||
public function sendEmails($template, array $users, array $data)
|
public function sendEmails($template, array $users, array $data)
|
||||||
{
|
{
|
||||||
try {
|
foreach ($users as $user) {
|
||||||
|
|
||||||
$author = '';
|
// Use the user language otherwise use the application language (do not use the session language)
|
||||||
|
if (! empty($user['language'])) {
|
||||||
if (Session::isOpen() && $this->userSession->isLogged()) {
|
Translator::load($user['language']);
|
||||||
$author = e('%s via Kanboard', $this->user->getFullname($this->session['user']));
|
}
|
||||||
|
else {
|
||||||
|
Translator::load($this->config->get('application_language', 'en_US'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$mailer = Swift_Mailer::newInstance($this->container['mailer']);
|
$this->emailClient->send(
|
||||||
|
$user['email'],
|
||||||
foreach ($users as $user) {
|
$user['name'] ?: $user['username'],
|
||||||
|
$this->getMailSubject($template, $data),
|
||||||
$this->container['logger']->debug('Send email notification to '.$user['username'].' lang='.$user['language']);
|
$this->getMailContent($template, $data)
|
||||||
$start_time = microtime(true);
|
);
|
||||||
|
|
||||||
// Use the user language otherwise use the application language (do not use the session language)
|
|
||||||
if (! empty($user['language'])) {
|
|
||||||
Translator::load($user['language']);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Translator::load($this->config->get('application_language', 'en_US'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the message
|
|
||||||
$message = Swift_Message::newInstance()
|
|
||||||
->setSubject($this->getMailSubject($template, $data))
|
|
||||||
->setFrom(array(MAIL_FROM => $author ?: 'Kanboard'))
|
|
||||||
->setBody($this->getMailContent($template, $data), 'text/html')
|
|
||||||
->setTo(array($user['email'] => $user['name'] ?: $user['username']));
|
|
||||||
|
|
||||||
$mailer->send($message);
|
|
||||||
|
|
||||||
$this->container['logger']->debug('Email sent in '.round(microtime(true) - $start_time, 6).' seconds');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Swift_TransportException $e) {
|
|
||||||
$this->container['logger']->error($e->getMessage());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore locales
|
// Restore locales
|
||||||
@@ -167,40 +143,40 @@ class Notification extends Base
|
|||||||
{
|
{
|
||||||
switch ($template) {
|
switch ($template) {
|
||||||
case 'file_creation':
|
case 'file_creation':
|
||||||
$subject = $this->getStandardMailSubject(t('New attachment'), $data);
|
$subject = $this->getStandardMailSubject(e('New attachment'), $data);
|
||||||
break;
|
break;
|
||||||
case 'comment_creation':
|
case 'comment_creation':
|
||||||
$subject = $this->getStandardMailSubject(t('New comment'), $data);
|
$subject = $this->getStandardMailSubject(e('New comment'), $data);
|
||||||
break;
|
break;
|
||||||
case 'comment_update':
|
case 'comment_update':
|
||||||
$subject = $this->getStandardMailSubject(t('Comment updated'), $data);
|
$subject = $this->getStandardMailSubject(e('Comment updated'), $data);
|
||||||
break;
|
break;
|
||||||
case 'subtask_creation':
|
case 'subtask_creation':
|
||||||
$subject = $this->getStandardMailSubject(t('New subtask'), $data);
|
$subject = $this->getStandardMailSubject(e('New subtask'), $data);
|
||||||
break;
|
break;
|
||||||
case 'subtask_update':
|
case 'subtask_update':
|
||||||
$subject = $this->getStandardMailSubject(t('Subtask updated'), $data);
|
$subject = $this->getStandardMailSubject(e('Subtask updated'), $data);
|
||||||
break;
|
break;
|
||||||
case 'task_creation':
|
case 'task_creation':
|
||||||
$subject = $this->getStandardMailSubject(t('New task'), $data);
|
$subject = $this->getStandardMailSubject(e('New task'), $data);
|
||||||
break;
|
break;
|
||||||
case 'task_update':
|
case 'task_update':
|
||||||
$subject = $this->getStandardMailSubject(t('Task updated'), $data);
|
$subject = $this->getStandardMailSubject(e('Task updated'), $data);
|
||||||
break;
|
break;
|
||||||
case 'task_close':
|
case 'task_close':
|
||||||
$subject = $this->getStandardMailSubject(t('Task closed'), $data);
|
$subject = $this->getStandardMailSubject(e('Task closed'), $data);
|
||||||
break;
|
break;
|
||||||
case 'task_open':
|
case 'task_open':
|
||||||
$subject = $this->getStandardMailSubject(t('Task opened'), $data);
|
$subject = $this->getStandardMailSubject(e('Task opened'), $data);
|
||||||
break;
|
break;
|
||||||
case 'task_move_column':
|
case 'task_move_column':
|
||||||
$subject = $this->getStandardMailSubject(t('Column Change'), $data);
|
$subject = $this->getStandardMailSubject(e('Column Change'), $data);
|
||||||
break;
|
break;
|
||||||
case 'task_move_position':
|
case 'task_move_position':
|
||||||
$subject = $this->getStandardMailSubject(t('Position Change'), $data);
|
$subject = $this->getStandardMailSubject(e('Position Change'), $data);
|
||||||
break;
|
break;
|
||||||
case 'task_assignee_change':
|
case 'task_assignee_change':
|
||||||
$subject = $this->getStandardMailSubject(t('Assignee Change'), $data);
|
$subject = $this->getStandardMailSubject(e('Assignee Change'), $data);
|
||||||
break;
|
break;
|
||||||
case 'task_due':
|
case 'task_due':
|
||||||
$subject = e('[%s][Due tasks]', $data['project']);
|
$subject = e('[%s][Due tasks]', $data['project']);
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ class ClassProvider implements ServiceProviderInterface
|
|||||||
'Webhook',
|
'Webhook',
|
||||||
),
|
),
|
||||||
'Core' => array(
|
'Core' => array(
|
||||||
|
'EmailClient',
|
||||||
'Helper',
|
'Helper',
|
||||||
'HttpClient',
|
'HttpClient',
|
||||||
'MemoryCache',
|
'MemoryCache',
|
||||||
@@ -78,9 +79,10 @@ class ClassProvider implements ServiceProviderInterface
|
|||||||
'HipchatWebhook',
|
'HipchatWebhook',
|
||||||
'Jabber',
|
'Jabber',
|
||||||
'MailgunWebhook',
|
'MailgunWebhook',
|
||||||
'PostmarkWebhook',
|
'Postmark',
|
||||||
'SendgridWebhook',
|
'SendgridWebhook',
|
||||||
'SlackWebhook',
|
'SlackWebhook',
|
||||||
|
'Smtp',
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace ServiceProvider;
|
|
||||||
|
|
||||||
use Pimple\Container;
|
|
||||||
use Pimple\ServiceProviderInterface;
|
|
||||||
use Swift_SmtpTransport;
|
|
||||||
use Swift_SendmailTransport;
|
|
||||||
use Swift_MailTransport;
|
|
||||||
|
|
||||||
class MailerProvider implements ServiceProviderInterface
|
|
||||||
{
|
|
||||||
public function register(Container $container)
|
|
||||||
{
|
|
||||||
$container['mailer'] = function () {
|
|
||||||
switch (MAIL_TRANSPORT) {
|
|
||||||
case 'smtp':
|
|
||||||
$transport = Swift_SmtpTransport::newInstance(MAIL_SMTP_HOSTNAME, MAIL_SMTP_PORT);
|
|
||||||
$transport->setUsername(MAIL_SMTP_USERNAME);
|
|
||||||
$transport->setPassword(MAIL_SMTP_PASSWORD);
|
|
||||||
$transport->setEncryption(MAIL_SMTP_ENCRYPTION);
|
|
||||||
break;
|
|
||||||
case 'sendmail':
|
|
||||||
$transport = Swift_SendmailTransport::newInstance(MAIL_SENDMAIL_COMMAND);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
$transport = Swift_MailTransport::newInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $transport;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -27,4 +27,3 @@ $container->register(new ServiceProvider\LoggingProvider);
|
|||||||
$container->register(new ServiceProvider\DatabaseProvider);
|
$container->register(new ServiceProvider\DatabaseProvider);
|
||||||
$container->register(new ServiceProvider\ClassProvider);
|
$container->register(new ServiceProvider\ClassProvider);
|
||||||
$container->register(new ServiceProvider\EventDispatcherProvider);
|
$container->register(new ServiceProvider\EventDispatcherProvider);
|
||||||
$container->register(new ServiceProvider\MailerProvider);
|
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ defined('MAIL_SMTP_USERNAME') or define('MAIL_SMTP_USERNAME', '');
|
|||||||
defined('MAIL_SMTP_PASSWORD') or define('MAIL_SMTP_PASSWORD', '');
|
defined('MAIL_SMTP_PASSWORD') or define('MAIL_SMTP_PASSWORD', '');
|
||||||
defined('MAIL_SMTP_ENCRYPTION') or define('MAIL_SMTP_ENCRYPTION', null);
|
defined('MAIL_SMTP_ENCRYPTION') or define('MAIL_SMTP_ENCRYPTION', null);
|
||||||
defined('MAIL_SENDMAIL_COMMAND') or define('MAIL_SENDMAIL_COMMAND', '/usr/sbin/sendmail -bs');
|
defined('MAIL_SENDMAIL_COMMAND') or define('MAIL_SENDMAIL_COMMAND', '/usr/sbin/sendmail -bs');
|
||||||
|
defined('POSTMARK_API_TOKEN') or define('POSTMARK_API_TOKEN', '');
|
||||||
|
|
||||||
// Enable or disable "Strict-Transport-Security" HTTP header
|
// Enable or disable "Strict-Transport-Security" HTTP header
|
||||||
defined('ENABLE_HSTS') or define('ENABLE_HSTS', true);
|
defined('ENABLE_HSTS') or define('ENABLE_HSTS', true);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ define('FILES_DIR', 'data/files/');
|
|||||||
// E-mail address for the "From" header (notifications)
|
// E-mail address for the "From" header (notifications)
|
||||||
define('MAIL_FROM', 'notifications@kanboard.local');
|
define('MAIL_FROM', 'notifications@kanboard.local');
|
||||||
|
|
||||||
// Mail transport to use: "smtp", "sendmail" or "mail" (PHP mail function)
|
// Mail transport to use: "smtp", "sendmail", "mail" (PHP mail function), "postmark"
|
||||||
define('MAIL_TRANSPORT', 'mail');
|
define('MAIL_TRANSPORT', 'mail');
|
||||||
|
|
||||||
// SMTP configuration to use when the "smtp" transport is chosen
|
// SMTP configuration to use when the "smtp" transport is chosen
|
||||||
@@ -27,6 +27,9 @@ define('MAIL_SMTP_ENCRYPTION', null); // Valid values are "null", "ssl" or "tls"
|
|||||||
// Sendmail command to use when the transport is "sendmail"
|
// Sendmail command to use when the transport is "sendmail"
|
||||||
define('MAIL_SENDMAIL_COMMAND', '/usr/sbin/sendmail -bs');
|
define('MAIL_SENDMAIL_COMMAND', '/usr/sbin/sendmail -bs');
|
||||||
|
|
||||||
|
// Postmark API token (used to send emails through their API)
|
||||||
|
define('POSTMARK_API_TOKEN', '');
|
||||||
|
|
||||||
// Database driver: sqlite, mysql or postgres (sqlite by default)
|
// Database driver: sqlite, mysql or postgres (sqlite by default)
|
||||||
define('DB_DRIVER', 'sqlite');
|
define('DB_DRIVER', 'sqlite');
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,16 @@ To receive email notifications, users of Kanboard must have:
|
|||||||
|
|
||||||
Note: The logged user who performs the action doesn't receive any notifications, only other project members.
|
Note: The logged user who performs the action doesn't receive any notifications, only other project members.
|
||||||
|
|
||||||
|
Email transports
|
||||||
|
----------------
|
||||||
|
|
||||||
|
There are several email transports available:
|
||||||
|
|
||||||
|
- SMTP
|
||||||
|
- Sendmail
|
||||||
|
- PHP native mail function
|
||||||
|
- Postmark
|
||||||
|
|
||||||
Server settings
|
Server settings
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
@@ -57,6 +67,34 @@ define('MAIL_TRANSPORT', 'sendmail');
|
|||||||
define('MAIL_SENDMAIL_COMMAND', '/usr/sbin/sendmail -bs');
|
define('MAIL_SENDMAIL_COMMAND', '/usr/sbin/sendmail -bs');
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### PHP native mail function
|
||||||
|
|
||||||
|
This is the default configuration:
|
||||||
|
|
||||||
|
```php
|
||||||
|
define('MAIL_TRANSPORT', 'mail');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Postmark HTTP API
|
||||||
|
|
||||||
|
Postmark is a third-party email service.
|
||||||
|
If you already use the Postmark integration to receive emails in Kanboard you can use the same provider to send email too.
|
||||||
|
|
||||||
|
This system use their HTTP API instead of the SMTP protocol.
|
||||||
|
|
||||||
|
Here are the required settings for this configuration:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// We choose "postmark" as mail transport
|
||||||
|
define('MAIL_TRANSPORT', 'postmark');
|
||||||
|
|
||||||
|
// Copy and paste your Postmark API token
|
||||||
|
define('POSTMARK_API_TOKEN', 'COPY HERE YOUR POSTMARK API TOKEN');
|
||||||
|
|
||||||
|
// Be sure to use the Postmark configured sender email address
|
||||||
|
define('MAIL_FROM', 'sender-address-configured-in-postmark@example.org');
|
||||||
|
```
|
||||||
|
|
||||||
### The sender email address
|
### The sender email address
|
||||||
|
|
||||||
By default, emails will use the sender address `notifications@kanboard.local`.
|
By default, emails will use the sender address `notifications@kanboard.local`.
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class FakeHttpClient
|
|||||||
{
|
{
|
||||||
private $url = '';
|
private $url = '';
|
||||||
private $data = array();
|
private $data = array();
|
||||||
|
private $headers = array();
|
||||||
|
|
||||||
public function getUrl()
|
public function getUrl()
|
||||||
{
|
{
|
||||||
@@ -26,16 +27,21 @@ class FakeHttpClient
|
|||||||
return $this->data;
|
return $this->data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getHeaders()
|
||||||
|
{
|
||||||
|
return $this->headers;
|
||||||
|
}
|
||||||
|
|
||||||
public function toPrettyJson()
|
public function toPrettyJson()
|
||||||
{
|
{
|
||||||
return json_encode($this->data, JSON_PRETTY_PRINT);
|
return json_encode($this->data, JSON_PRETTY_PRINT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function post($url, array $data)
|
public function post($url, array $data, array $headers = array())
|
||||||
{
|
{
|
||||||
$this->url = $url;
|
$this->url = $url;
|
||||||
$this->data = $data;
|
$this->data = $data;
|
||||||
//echo $this->toPrettyJson();
|
$this->headers = $headers;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,41 @@
|
|||||||
|
|
||||||
require_once __DIR__.'/Base.php';
|
require_once __DIR__.'/Base.php';
|
||||||
|
|
||||||
use Integration\PostmarkWebhook;
|
use Integration\Postmark;
|
||||||
use Model\TaskCreation;
|
use Model\TaskCreation;
|
||||||
use Model\TaskFinder;
|
use Model\TaskFinder;
|
||||||
use Model\Project;
|
use Model\Project;
|
||||||
use Model\ProjectPermission;
|
use Model\ProjectPermission;
|
||||||
use Model\User;
|
use Model\User;
|
||||||
|
|
||||||
class PostmarkWebhookTest extends Base
|
class PostmarkTest extends Base
|
||||||
{
|
{
|
||||||
|
public function testSendEmail()
|
||||||
|
{
|
||||||
|
$pm = new Postmark($this->container);
|
||||||
|
$pm->sendEmail('test@localhost', 'Me', 'Test', 'Content', 'Bob');
|
||||||
|
|
||||||
|
$this->assertEquals('https://api.postmarkapp.com/email', $this->container['httpClient']->getUrl());
|
||||||
|
|
||||||
|
$data = $this->container['httpClient']->getData();
|
||||||
|
|
||||||
|
$this->assertArrayHasKey('From', $data);
|
||||||
|
$this->assertArrayHasKey('To', $data);
|
||||||
|
$this->assertArrayHasKey('Subject', $data);
|
||||||
|
$this->assertArrayHasKey('HtmlBody', $data);
|
||||||
|
|
||||||
|
$this->assertEquals('Me <test@localhost>', $data['To']);
|
||||||
|
$this->assertEquals('Bob <notifications@kanboard.local>', $data['From']);
|
||||||
|
$this->assertEquals('Test', $data['Subject']);
|
||||||
|
$this->assertEquals('Content', $data['HtmlBody']);
|
||||||
|
|
||||||
|
$this->assertContains('Accept: application/json', $this->container['httpClient']->getHeaders());
|
||||||
|
$this->assertContains('X-Postmark-Server-Token: ', $this->container['httpClient']->getHeaders());
|
||||||
|
}
|
||||||
|
|
||||||
public function testHandlePayload()
|
public function testHandlePayload()
|
||||||
{
|
{
|
||||||
$w = new PostmarkWebhook($this->container);
|
$w = new Postmark($this->container);
|
||||||
$p = new Project($this->container);
|
$p = new Project($this->container);
|
||||||
$pp = new ProjectPermission($this->container);
|
$pp = new ProjectPermission($this->container);
|
||||||
$u = new User($this->container);
|
$u = new User($this->container);
|
||||||
@@ -26,20 +49,20 @@ class PostmarkWebhookTest extends Base
|
|||||||
$this->assertEquals(2, $p->create(array('name' => 'test2', 'identifier' => 'TEST1')));
|
$this->assertEquals(2, $p->create(array('name' => 'test2', 'identifier' => 'TEST1')));
|
||||||
|
|
||||||
// Empty payload
|
// Empty payload
|
||||||
$this->assertFalse($w->parsePayload(array()));
|
$this->assertFalse($w->receiveEmail(array()));
|
||||||
|
|
||||||
// Unknown user
|
// Unknown user
|
||||||
$this->assertFalse($w->parsePayload(array('From' => 'a@b.c', 'Subject' => 'Email task', 'MailboxHash' => 'foobar', 'TextBody' => 'boo')));
|
$this->assertFalse($w->receiveEmail(array('From' => 'a@b.c', 'Subject' => 'Email task', 'MailboxHash' => 'foobar', 'TextBody' => 'boo')));
|
||||||
|
|
||||||
// Project not found
|
// Project not found
|
||||||
$this->assertFalse($w->parsePayload(array('From' => 'me@localhost', 'Subject' => 'Email task', 'MailboxHash' => 'test', 'TextBody' => 'boo')));
|
$this->assertFalse($w->receiveEmail(array('From' => 'me@localhost', 'Subject' => 'Email task', 'MailboxHash' => 'test', 'TextBody' => 'boo')));
|
||||||
|
|
||||||
// User is not member
|
// User is not member
|
||||||
$this->assertFalse($w->parsePayload(array('From' => 'me@localhost', 'Subject' => 'Email task', 'MailboxHash' => 'test1', 'TextBody' => 'boo')));
|
$this->assertFalse($w->receiveEmail(array('From' => 'me@localhost', 'Subject' => 'Email task', 'MailboxHash' => 'test1', 'TextBody' => 'boo')));
|
||||||
$this->assertTrue($pp->addMember(2, 2));
|
$this->assertTrue($pp->addMember(2, 2));
|
||||||
|
|
||||||
// The task must be created
|
// The task must be created
|
||||||
$this->assertTrue($w->parsePayload(array('From' => 'me@localhost', 'Subject' => 'Email task', 'MailboxHash' => 'test1', 'TextBody' => 'boo')));
|
$this->assertTrue($w->receiveEmail(array('From' => 'me@localhost', 'Subject' => 'Email task', 'MailboxHash' => 'test1', 'TextBody' => 'boo')));
|
||||||
|
|
||||||
$task = $tf->getById(1);
|
$task = $tf->getById(1);
|
||||||
$this->assertNotEmpty($task);
|
$this->assertNotEmpty($task);
|
||||||
@@ -51,7 +74,7 @@ class PostmarkWebhookTest extends Base
|
|||||||
|
|
||||||
public function testHtml2Markdown()
|
public function testHtml2Markdown()
|
||||||
{
|
{
|
||||||
$w = new PostmarkWebhook($this->container);
|
$w = new Postmark($this->container);
|
||||||
$p = new Project($this->container);
|
$p = new Project($this->container);
|
||||||
$pp = new ProjectPermission($this->container);
|
$pp = new ProjectPermission($this->container);
|
||||||
$u = new User($this->container);
|
$u = new User($this->container);
|
||||||
@@ -62,7 +85,7 @@ class PostmarkWebhookTest extends Base
|
|||||||
$this->assertEquals(1, $p->create(array('name' => 'test2', 'identifier' => 'TEST1')));
|
$this->assertEquals(1, $p->create(array('name' => 'test2', 'identifier' => 'TEST1')));
|
||||||
$this->assertTrue($pp->addMember(1, 2));
|
$this->assertTrue($pp->addMember(1, 2));
|
||||||
|
|
||||||
$this->assertTrue($w->parsePayload(array('From' => 'me@localhost', 'Subject' => 'Email task', 'MailboxHash' => 'test1', 'TextBody' => 'boo', 'HtmlBody' => '<p><strong>boo</strong></p>')));
|
$this->assertTrue($w->receiveEmail(array('From' => 'me@localhost', 'Subject' => 'Email task', 'MailboxHash' => 'test1', 'TextBody' => 'boo', 'HtmlBody' => '<p><strong>boo</strong></p>')));
|
||||||
|
|
||||||
$task = $tf->getById(1);
|
$task = $tf->getById(1);
|
||||||
$this->assertNotEmpty($task);
|
$this->assertNotEmpty($task);
|
||||||
@@ -71,7 +94,7 @@ class PostmarkWebhookTest extends Base
|
|||||||
$this->assertEquals('**boo**', $task['description']);
|
$this->assertEquals('**boo**', $task['description']);
|
||||||
$this->assertEquals(2, $task['creator_id']);
|
$this->assertEquals(2, $task['creator_id']);
|
||||||
|
|
||||||
$this->assertTrue($w->parsePayload(array('From' => 'me@localhost', 'Subject' => 'Email task', 'MailboxHash' => 'test1', 'TextBody' => '**boo**', 'HtmlBody' => '')));
|
$this->assertTrue($w->receiveEmail(array('From' => 'me@localhost', 'Subject' => 'Email task', 'MailboxHash' => 'test1', 'TextBody' => '**boo**', 'HtmlBody' => '')));
|
||||||
|
|
||||||
$task = $tf->getById(2);
|
$task = $tf->getById(2);
|
||||||
$this->assertNotEmpty($task);
|
$this->assertNotEmpty($task);
|
||||||
Reference in New Issue
Block a user