Add Sendgrid integration (incoming email handling)
This commit is contained in:
parent
ac86c3100a
commit
f190be9e2d
|
|
@ -84,6 +84,7 @@ Documentation
|
|||
- [Task links](docs/task-links.markdown)
|
||||
- [Transitions](docs/transitions.markdown)
|
||||
- [Time tracking](docs/time-tracking.markdown)
|
||||
- [Create tasks by email](docs/create-tasks-by-email.markdown)
|
||||
|
||||
#### Working with users
|
||||
|
||||
|
|
@ -108,6 +109,7 @@ Documentation
|
|||
- [Gitlab webhooks](docs/gitlab-webhooks.markdown)
|
||||
- [Hipchat](docs/hipchat.markdown)
|
||||
- [Mailgun](docs/mailgun.markdown)
|
||||
- [Sendgrid](docs/sendgrid.markdown)
|
||||
- [Slack](docs/slack.markdown)
|
||||
- [Postmark](docs/postmark.markdown)
|
||||
|
||||
|
|
|
|||
|
|
@ -128,4 +128,18 @@ class Webhook extends Base
|
|||
|
||||
echo $this->mailgunWebhook->parsePayload($_POST) ? 'PARSED' : 'IGNORED';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Sendgrid webhooks
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function sendgrid()
|
||||
{
|
||||
if ($this->config->get('webhook_token') !== $this->request->getStringParam('token')) {
|
||||
$this->response->text('Not Authorized', 401);
|
||||
}
|
||||
|
||||
echo $this->sendgridWebhook->parsePayload($_POST) ? 'PARSED' : 'IGNORED';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,4 +31,24 @@ class Tool
|
|||
fclose($fp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mailbox hash from an email address
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @param string $email
|
||||
* @return string
|
||||
*/
|
||||
public static function getMailboxHash($email)
|
||||
{
|
||||
if (! strpos($email, '@') || ! strpos($email, '+')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
list($local_part,) = explode('@', $email);
|
||||
list(,$identifier) = explode('+', $local_part);
|
||||
|
||||
return $identifier;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace Integration;
|
||||
|
||||
use HTML_To_Markdown;
|
||||
use Core\Tool;
|
||||
|
||||
/**
|
||||
* Mailgun Webhook
|
||||
|
|
@ -21,7 +22,7 @@ class MailgunWebhook extends Base
|
|||
*/
|
||||
public function parsePayload(array $payload)
|
||||
{
|
||||
if (empty($payload['sender']) || empty($payload['subject']) || empty($payload['recipient']) || empty($payload['stripped-text'])) {
|
||||
if (empty($payload['sender']) || empty($payload['subject']) || empty($payload['recipient'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -34,7 +35,7 @@ class MailgunWebhook extends Base
|
|||
}
|
||||
|
||||
// The project must have a short name
|
||||
$project = $this->project->getByIdentifier($this->getMailboxHash($payload['recipient']));
|
||||
$project = $this->project->getByIdentifier(Tool::getMailboxHash($payload['recipient']));
|
||||
|
||||
if (empty($project)) {
|
||||
$this->container['logger']->debug('MailgunWebhook: ignored => project not found');
|
||||
|
|
@ -48,12 +49,15 @@ class MailgunWebhook extends Base
|
|||
}
|
||||
|
||||
// Get the Markdown contents
|
||||
if (empty($payload['stripped-html'])) {
|
||||
if (! empty($payload['stripped-html'])) {
|
||||
$markdown = new HTML_To_Markdown($payload['stripped-html'], array('strip_tags' => true));
|
||||
$description = $markdown->output();
|
||||
}
|
||||
else if (! empty($payload['stripped-text'])) {
|
||||
$description = $payload['stripped-text'];
|
||||
}
|
||||
else {
|
||||
$markdown = new HTML_To_Markdown($payload['stripped-html'], array('strip_tags' => true));
|
||||
$description = $markdown->output();
|
||||
$description = '';
|
||||
}
|
||||
|
||||
// Finally, we create the task
|
||||
|
|
@ -64,19 +68,4 @@ class MailgunWebhook extends Base
|
|||
'creator_id' => $user['id'],
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the project identifier
|
||||
*
|
||||
* @access public
|
||||
* @param string $email
|
||||
* @return string
|
||||
*/
|
||||
public function getMailboxHash($email)
|
||||
{
|
||||
list($local_part,) = explode('@', $email);
|
||||
list(,$identifier) = explode('+', $local_part);
|
||||
|
||||
return $identifier;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ class PostmarkWebhook extends Base
|
|||
*/
|
||||
public function parsePayload(array $payload)
|
||||
{
|
||||
if (empty($payload['From']) || empty($payload['Subject']) || empty($payload['MailboxHash']) || empty($payload['TextBody'])) {
|
||||
if (empty($payload['From']) || empty($payload['Subject']) || empty($payload['MailboxHash'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -48,12 +48,15 @@ class PostmarkWebhook extends Base
|
|||
}
|
||||
|
||||
// Get the Markdown contents
|
||||
if (empty($payload['HtmlBody'])) {
|
||||
if (! empty($payload['HtmlBody'])) {
|
||||
$markdown = new HTML_To_Markdown($payload['HtmlBody'], array('strip_tags' => true));
|
||||
$description = $markdown->output();
|
||||
}
|
||||
else if (! empty($payload['TextBody'])) {
|
||||
$description = $payload['TextBody'];
|
||||
}
|
||||
else {
|
||||
$markdown = new HTML_To_Markdown($payload['HtmlBody'], array('strip_tags' => true));
|
||||
$description = $markdown->output();
|
||||
$description = '';
|
||||
}
|
||||
|
||||
// Finally, we create the task
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
namespace Integration;
|
||||
|
||||
use HTML_To_Markdown;
|
||||
use Core\Tool;
|
||||
|
||||
/**
|
||||
* Sendgrid Webhook
|
||||
*
|
||||
* @package integration
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class SendgridWebhook extends Base
|
||||
{
|
||||
/**
|
||||
* Parse incoming email
|
||||
*
|
||||
* @access public
|
||||
* @param array $payload Incoming email
|
||||
* @return boolean
|
||||
*/
|
||||
public function parsePayload(array $payload)
|
||||
{
|
||||
if (empty($payload['envelope']) || empty($payload['subject'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$envelope = json_decode($payload['envelope'], true);
|
||||
$sender = isset($envelope['to'][0]) ? $envelope['to'][0] : '';
|
||||
|
||||
// The user must exists in Kanboard
|
||||
$user = $this->user->getByEmail($envelope['from']);
|
||||
|
||||
if (empty($user)) {
|
||||
$this->container['logger']->debug('SendgridWebhook: ignored => user not found');
|
||||
return false;
|
||||
}
|
||||
|
||||
// The project must have a short name
|
||||
$project = $this->project->getByIdentifier(Tool::getMailboxHash($sender));
|
||||
|
||||
if (empty($project)) {
|
||||
$this->container['logger']->debug('SendgridWebhook: ignored => project not found');
|
||||
return false;
|
||||
}
|
||||
|
||||
// The user must be member of the project
|
||||
if (! $this->projectPermission->isMember($project['id'], $user['id'])) {
|
||||
$this->container['logger']->debug('SendgridWebhook: ignored => user is not member of the project');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the Markdown contents
|
||||
if (! empty($payload['html'])) {
|
||||
$markdown = new HTML_To_Markdown($payload['html'], array('strip_tags' => true));
|
||||
$description = $markdown->output();
|
||||
}
|
||||
else if (! empty($payload['text'])) {
|
||||
$description = $payload['text'];
|
||||
}
|
||||
else {
|
||||
$description = '';
|
||||
}
|
||||
|
||||
// Finally, we create the task
|
||||
return (bool) $this->taskCreation->create(array(
|
||||
'project_id' => $project['id'],
|
||||
'title' => $payload['subject'],
|
||||
'description' => $description,
|
||||
'creator_id' => $user['id'],
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -864,4 +864,6 @@ return array(
|
|||
// 'Help on Postmark integration' => '',
|
||||
// 'Mailgun (incoming emails)' => '',
|
||||
// 'Help on Mailgun integration' => '',
|
||||
// 'Sendgrid (incoming emails)' => '',
|
||||
// 'Help on Sendgrid integration' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -864,4 +864,6 @@ return array(
|
|||
// 'Help on Postmark integration' => '',
|
||||
// 'Mailgun (incoming emails)' => '',
|
||||
// 'Help on Mailgun integration' => '',
|
||||
// 'Sendgrid (incoming emails)' => '',
|
||||
// 'Help on Sendgrid integration' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -864,4 +864,6 @@ return array(
|
|||
// 'Help on Postmark integration' => '',
|
||||
// 'Mailgun (incoming emails)' => '',
|
||||
// 'Help on Mailgun integration' => '',
|
||||
// 'Sendgrid (incoming emails)' => '',
|
||||
// 'Help on Sendgrid integration' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -864,4 +864,6 @@ return array(
|
|||
// 'Help on Postmark integration' => '',
|
||||
// 'Mailgun (incoming emails)' => '',
|
||||
// 'Help on Mailgun integration' => '',
|
||||
// 'Sendgrid (incoming emails)' => '',
|
||||
// 'Help on Sendgrid integration' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -866,4 +866,6 @@ return array(
|
|||
'Help on Postmark integration' => 'Aide sur l\'intégration avec Postmark',
|
||||
'Mailgun (incoming emails)' => 'Mailgun (emails entrants)',
|
||||
'Help on Mailgun integration' => 'Aide sur l\'intégration avec Mailgun',
|
||||
'Sendgrid (incoming emails)' => 'Sendgrid (emails entrants)',
|
||||
'Help on Sendgrid integration' => 'Aide sur l\'intégration avec Sendgrid',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -864,4 +864,6 @@ return array(
|
|||
// 'Help on Postmark integration' => '',
|
||||
// 'Mailgun (incoming emails)' => '',
|
||||
// 'Help on Mailgun integration' => '',
|
||||
// 'Sendgrid (incoming emails)' => '',
|
||||
// 'Help on Sendgrid integration' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -864,4 +864,6 @@ return array(
|
|||
// 'Help on Postmark integration' => '',
|
||||
// 'Mailgun (incoming emails)' => '',
|
||||
// 'Help on Mailgun integration' => '',
|
||||
// 'Sendgrid (incoming emails)' => '',
|
||||
// 'Help on Sendgrid integration' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -864,4 +864,6 @@ return array(
|
|||
// 'Help on Postmark integration' => '',
|
||||
// 'Mailgun (incoming emails)' => '',
|
||||
// 'Help on Mailgun integration' => '',
|
||||
// 'Sendgrid (incoming emails)' => '',
|
||||
// 'Help on Sendgrid integration' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -864,4 +864,6 @@ return array(
|
|||
// 'Help on Postmark integration' => '',
|
||||
// 'Mailgun (incoming emails)' => '',
|
||||
// 'Help on Mailgun integration' => '',
|
||||
// 'Sendgrid (incoming emails)' => '',
|
||||
// 'Help on Sendgrid integration' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -864,4 +864,6 @@ return array(
|
|||
// 'Help on Postmark integration' => '',
|
||||
// 'Mailgun (incoming emails)' => '',
|
||||
// 'Help on Mailgun integration' => '',
|
||||
// 'Sendgrid (incoming emails)' => '',
|
||||
// 'Help on Sendgrid integration' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -864,4 +864,6 @@ return array(
|
|||
// 'Help on Postmark integration' => '',
|
||||
// 'Mailgun (incoming emails)' => '',
|
||||
// 'Help on Mailgun integration' => '',
|
||||
// 'Sendgrid (incoming emails)' => '',
|
||||
// 'Help on Sendgrid integration' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -864,4 +864,6 @@ return array(
|
|||
// 'Help on Postmark integration' => '',
|
||||
// 'Mailgun (incoming emails)' => '',
|
||||
// 'Help on Mailgun integration' => '',
|
||||
// 'Sendgrid (incoming emails)' => '',
|
||||
// 'Help on Sendgrid integration' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -864,4 +864,6 @@ return array(
|
|||
// 'Help on Postmark integration' => '',
|
||||
// 'Mailgun (incoming emails)' => '',
|
||||
// 'Help on Mailgun integration' => '',
|
||||
// 'Sendgrid (incoming emails)' => '',
|
||||
// 'Help on Sendgrid integration' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -864,4 +864,6 @@ return array(
|
|||
// 'Help on Postmark integration' => '',
|
||||
// 'Mailgun (incoming emails)' => '',
|
||||
// 'Help on Mailgun integration' => '',
|
||||
// 'Sendgrid (incoming emails)' => '',
|
||||
// 'Help on Sendgrid integration' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -864,4 +864,6 @@ return array(
|
|||
// 'Help on Postmark integration' => '',
|
||||
// 'Mailgun (incoming emails)' => '',
|
||||
// 'Help on Mailgun integration' => '',
|
||||
// 'Sendgrid (incoming emails)' => '',
|
||||
// 'Help on Sendgrid integration' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -864,4 +864,6 @@ return array(
|
|||
// 'Help on Postmark integration' => '',
|
||||
// 'Mailgun (incoming emails)' => '',
|
||||
// 'Help on Mailgun integration' => '',
|
||||
// 'Sendgrid (incoming emails)' => '',
|
||||
// 'Help on Sendgrid integration' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -864,4 +864,6 @@ return array(
|
|||
// 'Help on Postmark integration' => '',
|
||||
// 'Mailgun (incoming emails)' => '',
|
||||
// 'Help on Mailgun integration' => '',
|
||||
// 'Sendgrid (incoming emails)' => '',
|
||||
// 'Help on Sendgrid integration' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -150,6 +150,10 @@ class User extends Base
|
|||
*/
|
||||
public function getByEmail($email)
|
||||
{
|
||||
if (empty($email)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->db->table(self::TABLE)->eq('email', $email)->findOne();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ class ClassProvider implements ServiceProviderInterface
|
|||
'BitbucketWebhook',
|
||||
'Hipchat',
|
||||
'MailgunWebhook',
|
||||
'SendgridWebhook',
|
||||
'SlackWebhook',
|
||||
'PostmarkWebhook',
|
||||
)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,12 @@
|
|||
<p class="form-help"><a href="http://kanboard.net/documentation/mailgun" target="_blank"><?= t('Help on Mailgun integration') ?></a></p>
|
||||
</div>
|
||||
|
||||
<h3><img src="assets/img/sendgrid-icon.png"/> <?= t('Sendgrid (incoming emails)') ?></h3>
|
||||
<div class="listing">
|
||||
<input type="text" class="auto-select" readonly="readonly" value="<?= $this->getCurrentBaseUrl().$this->u('webhook', 'sendgrid', array('token' => $values['webhook_token'])) ?>"/><br/>
|
||||
<p class="form-help"><a href="http://kanboard.net/documentation/sendgrid" target="_blank"><?= t('Help on Sendgrid integration') ?></a></p>
|
||||
</div>
|
||||
|
||||
<h3><img src="assets/img/postmark-icon.png"/> <?= t('Postmark (incoming emails)') ?></h3>
|
||||
<div class="listing">
|
||||
<input type="text" class="auto-select" readonly="readonly" value="<?= $this->getCurrentBaseUrl().$this->u('webhook', 'postmark', array('token' => $values['webhook_token'])) ?>"/><br/>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 646 B After Width: | Height: | Size: 646 B |
|
Before Width: | Height: | Size: 872 B After Width: | Height: | Size: 872 B |
Binary file not shown.
|
After Width: | Height: | Size: 600 B |
|
|
@ -0,0 +1,44 @@
|
|||
Create tasks by email
|
||||
=====================
|
||||
|
||||
You can create tasks directly by sending an email.
|
||||
|
||||
At the moment, Kanboard is integrated with 3 external services:
|
||||
|
||||
- [Mailgun](http://kanboard.net/documentation/mailgun)
|
||||
- [Sendgrid](http://kanboard.net/documentation/sendgrid)
|
||||
- [Postmark](http://kanboard.net/documentation/postmark)
|
||||
|
||||
These services handle incoming emails without having to configure any SMTP server.
|
||||
|
||||
When an email is received, Kanboard receive the message on a specific end-point.
|
||||
All complicated works are already handled by those services.
|
||||
|
||||
Incoming emails workflow
|
||||
------------------------
|
||||
|
||||
1. You send an email to a specific address, by example **something+myproject@inbound.mydomain.tld**
|
||||
2. Your email is forwarded to the third-party SMTP servers
|
||||
3. The SMTP provider call the Kanboard webhook with the email in JSON or multipart/form-data formats
|
||||
4. Kanboard parse the received email and create the task to the right project
|
||||
|
||||
Note: New tasks are automatically created in the first column.
|
||||
|
||||
Email format
|
||||
------------
|
||||
|
||||
- The local part of the email address must use the plus separator, by example **kanboard+project123**
|
||||
- The string defined after the plus sign must match a project identifier, by example **project123** is the identifier of the project **Project 123**
|
||||
- The email subject becomes the task title
|
||||
- The email body becomes the task description (Markdown format)
|
||||
|
||||
Incoming emails can be written in text or HTML formats.
|
||||
**Kanboard is able to convert simple HTML emails to Markdown**.
|
||||
|
||||
Security and requirements
|
||||
-------------------------
|
||||
|
||||
- The Kanboard webhook is protected by a random token
|
||||
- The sender email address must match a Kanboard user
|
||||
- The Kanboard project must have a unique identifier, by example **MYPROJECT**
|
||||
- The Kanboard user must be member of the project
|
||||
|
|
@ -6,34 +6,7 @@ You can use the service [Mailgun](http://www.mailgun.com/) to create tasks direc
|
|||
This integration works with the inbound email service of Mailgun (routes).
|
||||
Kanboard use a webhook to handle incoming emails.
|
||||
|
||||
Incoming emails workflow
|
||||
------------------------
|
||||
|
||||
1. You send an email to a specific address, by example **something+myproject@inbound.mydomain.tld**
|
||||
2. Your email is forwarded to Mailgun SMTP servers
|
||||
3. Mailgun call the Kanboard webhook with the email in JSON format
|
||||
4. Kanboard parse the received email and create the task to the right project
|
||||
|
||||
Note: New tasks are automatically created in the first column.
|
||||
|
||||
Email format
|
||||
------------
|
||||
|
||||
- The local part of the email address must use the plus separator, by example **kanboard+project123**
|
||||
- The string defined after the plus sign must match a project identifier, by example **project123** is the identifier of the project **Project 123**
|
||||
- The email subject becomes the task title
|
||||
- The email body becomes the task description (Markdown format)
|
||||
|
||||
Incoming emails can be written in text or HTML formats.
|
||||
**Kanboard is able to convert simple HTML emails to Markdown**.
|
||||
|
||||
Security and requirements
|
||||
-------------------------
|
||||
|
||||
- The Kanboard webhook is protected by a random token
|
||||
- The sender email address must match a Kanboard user
|
||||
- The Kanboard project must have a unique identifier, by example **MYPROJECT**
|
||||
- The Kanboard user must be member of the project
|
||||
The [incoming email workflow is described here](http://kanboard.net/documentation/email-tasks).
|
||||
|
||||
Mailgun configuration
|
||||
---------------------
|
||||
|
|
@ -53,9 +26,3 @@ Kanboard configuration
|
|||
1. Be sure that your users have an email address in their profiles
|
||||
2. Assign a project identifier to the desired projects: **Project settings > Edit**
|
||||
3. Try to send an email to your project
|
||||
|
||||
Troubleshootings
|
||||
----------------
|
||||
|
||||
- Test if your route match in the console
|
||||
- Double-check requirements mentioned above
|
||||
|
|
|
|||
|
|
@ -6,40 +6,15 @@ You can use the service [Postmark](https://postmarkapp.com/) to create tasks dir
|
|||
This integration works with the inbound email service of Postmark.
|
||||
Kanboard use a webhook to handle incoming emails.
|
||||
|
||||
Incoming emails workflow
|
||||
------------------------
|
||||
|
||||
1. You send an email to a specific address, by example **something+myproject@inbound.mydomain.tld**
|
||||
2. Your email is forwarded to Postmark SMTP servers
|
||||
3. Postmark call the Kanboard webhook with the email in JSON format
|
||||
4. Kanboard parse the received email and create the task to the right project
|
||||
|
||||
Note: New tasks are automatically created in the first column.
|
||||
|
||||
Email format
|
||||
------------
|
||||
|
||||
- The local part of the email address must use the plus separator, by example **kanboard+project123**
|
||||
- The string defined after the plus sign must match a project identifier, by example **project123** is the identifier of the project **Project 123**
|
||||
- The email subject becomes the task title
|
||||
- The email body becomes the task description (Markdown format)
|
||||
|
||||
Incoming emails can be written in text or HTML formats.
|
||||
**Kanboard is able to convert simple HTML emails to Markdown**.
|
||||
|
||||
Security and requirements
|
||||
-------------------------
|
||||
|
||||
- The Kanboard webhook is protected by a random token
|
||||
- The sender email address (From header) must match a Kanboard user
|
||||
- The Kanboard project must have a unique identifier, by example **MYPROJECT**
|
||||
- The Kanboard user must be member of the project
|
||||
The [incoming email workflow is described here](http://kanboard.net/documentation/email-tasks).
|
||||
|
||||
Postmark configuration
|
||||
----------------------
|
||||
|
||||
- Follow the [official documentation about inbound email processing](http://developer.postmarkapp.com/developer-process-configure.html)
|
||||
- The Kanboard webhook url is displayed in **Settings > Integrations > Postmark**
|
||||
Just follow the [official documentation about inbound email processing](http://developer.postmarkapp.com/developer-process-configure.html).
|
||||
Basically, you have to forward your own domain or subdomain to a specific Postmark email address.
|
||||
|
||||
The Kanboard webhook url is displayed in **Settings > Integrations > Postmark**
|
||||
|
||||
Kanboard configuration
|
||||
----------------------
|
||||
|
|
@ -52,4 +27,3 @@ Troubleshootings
|
|||
----------------
|
||||
|
||||
- Test the webhook url from the Postmark console, you should have a status code `200 OK`
|
||||
- Double-check requirements mentioned above
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
Sendgrid
|
||||
========
|
||||
|
||||
You can use the service [Sendgrid](https://sendgrid.com/) to create tasks directly by email.
|
||||
|
||||
This integration works with the [Parse API of Sendgrid](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html).
|
||||
Kanboard use a webhook to handle incoming emails.
|
||||
|
||||
The [incoming email workflow is described here](http://kanboard.net/documentation/email-tasks).
|
||||
|
||||
Sendgrid configuration
|
||||
----------------------
|
||||
|
||||
1. Create a new domain or subdomain (by example **inbound.mydomain.tld**) with a MX record that point to **mx.sendgrid.net**
|
||||
2. Add your domain and the Kanboard webhook url to [the configuration page in Sendgrid](https://sendgrid.com/developer/reply)
|
||||
|
||||
The Kanboard webhook url is displayed in **Settings > Integrations > Sendgrid**
|
||||
|
||||
Kanboard configuration
|
||||
----------------------
|
||||
|
||||
1. Be sure that your users have an email address in their profiles
|
||||
2. Assign a project identifier to the desired projects: **Project settings > Edit**
|
||||
3. Try to send an email to your project
|
||||
|
|
@ -231,6 +231,9 @@ class ProjectTest extends Base
|
|||
$this->assertNotEmpty($project);
|
||||
$this->assertEquals('TEST1', $project['identifier']);
|
||||
|
||||
$project = $p->getByIdentifier('');
|
||||
$this->assertFalse($project);
|
||||
|
||||
// Validation rules
|
||||
$r = $p->validateCreation(array('name' => 'test', 'identifier' => 'TEST1'));
|
||||
$this->assertFalse($r[0]);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
|
||||
require_once __DIR__.'/Base.php';
|
||||
|
||||
use Integration\SendgridWebhook;
|
||||
use Model\TaskCreation;
|
||||
use Model\TaskFinder;
|
||||
use Model\Project;
|
||||
use Model\ProjectPermission;
|
||||
use Model\User;
|
||||
|
||||
class SendgridWebhookTest extends Base
|
||||
{
|
||||
public function testHandlePayload()
|
||||
{
|
||||
$w = new SendgridWebhook($this->container);
|
||||
$p = new Project($this->container);
|
||||
$pp = new ProjectPermission($this->container);
|
||||
$u = new User($this->container);
|
||||
$tc = new TaskCreation($this->container);
|
||||
$tf = new TaskFinder($this->container);
|
||||
|
||||
$this->assertEquals(2, $u->create(array('name' => 'me', 'email' => 'me@localhost')));
|
||||
|
||||
$this->assertEquals(1, $p->create(array('name' => 'test1')));
|
||||
$this->assertEquals(2, $p->create(array('name' => 'test2', 'identifier' => 'TEST1')));
|
||||
|
||||
// Empty payload
|
||||
$this->assertFalse($w->parsePayload(array()));
|
||||
|
||||
// Unknown user
|
||||
$this->assertFalse($w->parsePayload(array(
|
||||
'envelope' => '{"to":["a@b.c"],"from":"a.b.c"}',
|
||||
'subject' => 'Email task'
|
||||
)));
|
||||
|
||||
// Project not found
|
||||
$this->assertFalse($w->parsePayload(array(
|
||||
'envelope' => '{"to":["a@b.c"],"from":"me@localhost"}',
|
||||
'subject' => 'Email task'
|
||||
)));
|
||||
|
||||
// User is not member
|
||||
$this->assertFalse($w->parsePayload(array(
|
||||
'envelope' => '{"to":["something+test1@localhost"],"from":"me@localhost"}',
|
||||
'subject' => 'Email task'
|
||||
)));
|
||||
|
||||
$this->assertTrue($pp->addMember(2, 2));
|
||||
|
||||
// The task must be created
|
||||
$this->assertTrue($w->parsePayload(array(
|
||||
'envelope' => '{"to":["something+test1@localhost"],"from":"me@localhost"}',
|
||||
'subject' => 'Email task'
|
||||
)));
|
||||
|
||||
$task = $tf->getById(1);
|
||||
$this->assertNotEmpty($task);
|
||||
$this->assertEquals(2, $task['project_id']);
|
||||
$this->assertEquals('Email task', $task['title']);
|
||||
$this->assertEquals('', $task['description']);
|
||||
$this->assertEquals(2, $task['creator_id']);
|
||||
|
||||
// Html content
|
||||
$this->assertTrue($w->parsePayload(array(
|
||||
'envelope' => '{"to":["something+test1@localhost"],"from":"me@localhost"}',
|
||||
'subject' => 'Email task',
|
||||
'html' => '<strong>bold</strong> text',
|
||||
)));
|
||||
|
||||
$task = $tf->getById(2);
|
||||
$this->assertNotEmpty($task);
|
||||
$this->assertEquals(2, $task['project_id']);
|
||||
$this->assertEquals('Email task', $task['title']);
|
||||
$this->assertEquals('**bold** text', $task['description']);
|
||||
$this->assertEquals(2, $task['creator_id']);
|
||||
|
||||
// Text content
|
||||
$this->assertTrue($w->parsePayload(array(
|
||||
'envelope' => '{"to":["something+test1@localhost"],"from":"me@localhost"}',
|
||||
'subject' => 'Email task',
|
||||
'text' => '**bold** text',
|
||||
)));
|
||||
|
||||
$task = $tf->getById(3);
|
||||
$this->assertNotEmpty($task);
|
||||
$this->assertEquals(2, $task['project_id']);
|
||||
$this->assertEquals('Email task', $task['title']);
|
||||
$this->assertEquals('**bold** text', $task['description']);
|
||||
$this->assertEquals(2, $task['creator_id']);
|
||||
|
||||
// Text + html content
|
||||
$this->assertTrue($w->parsePayload(array(
|
||||
'envelope' => '{"to":["something+test1@localhost"],"from":"me@localhost"}',
|
||||
'subject' => 'Email task',
|
||||
'html' => '<strong>bold</strong> html',
|
||||
'text' => '**bold** text',
|
||||
)));
|
||||
|
||||
$task = $tf->getById(4);
|
||||
$this->assertNotEmpty($task);
|
||||
$this->assertEquals(2, $task['project_id']);
|
||||
$this->assertEquals('Email task', $task['title']);
|
||||
$this->assertEquals('**bold** html', $task['description']);
|
||||
$this->assertEquals(2, $task['creator_id']);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
require_once __DIR__.'/Base.php';
|
||||
|
||||
use Core\Tool;
|
||||
|
||||
class ToolTest extends Base
|
||||
{
|
||||
public function testMailboxHash()
|
||||
{
|
||||
$this->assertEquals('test1', Tool::getMailboxHash('a+test1@localhost'));
|
||||
$this->assertEquals('', Tool::getMailboxHash('test1@localhost'));
|
||||
$this->assertEquals('', Tool::getMailboxHash('test1'));
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,16 @@ use Model\Project;
|
|||
|
||||
class UserTest extends Base
|
||||
{
|
||||
public function testGetByEmail()
|
||||
{
|
||||
$u = new User($this->container);
|
||||
$this->assertNotFalse($u->create(array('username' => 'user1', 'password' => '123456', 'email' => 'user1@localhost')));
|
||||
$this->assertNotFalse($u->create(array('username' => 'user2', 'password' => '123456', 'email' => '')));
|
||||
|
||||
$this->assertNotEmpty($u->getByEmail('user1@localhost'));
|
||||
$this->assertEmpty($u->getByEmail(''));
|
||||
}
|
||||
|
||||
public function testPassword()
|
||||
{
|
||||
$password = 'test123';
|
||||
|
|
|
|||
Loading…
Reference in New Issue