Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ElasticEmail Service #24

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"php": "^7.2.5",
"ext-json": "*",
"aws/aws-sdk-php-laravel": "^3.4",
"elastic-email/web-api-client": "^1.0",
"illuminate/support": "^7.0",
"kriswallsmith/buzz": "^1.1",
"laravel/ui": "^2.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Sendportal\Base\Models\EmailServiceType;

class AddElasticEmailServiceType extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
EmailServiceType::unguard();

EmailServiceType::create([
'id' => EmailServiceType::ELASTIC,
'name' => 'ElasticEmail',
]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{!! Form::textField('settings[key]', __('API Key'), \Arr::get($settings ?? [], 'key'), ['autocomplete' => 'off']) !!}
1 change: 1 addition & 0 deletions routes/api.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
$webhookRouter->post('mailgun', 'MailgunWebhooksController@handle')->name('mailgun');
$webhookRouter->post('postmark', 'PostmarkWebhooksController@handle')->name('postmark');
$webhookRouter->post('sendgrid', 'SendgridWebhooksController@handle')->name('sendgrid');
$webhookRouter->get('elastic', 'ElasticWebhooksController@handle')->name('elastic');
});

Route::get('ping', static function () {
Expand Down
95 changes: 95 additions & 0 deletions src/Adapters/ElasticMailAdapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php
/**
* Criado por Maizer Aly de O. Gomes para sendportal-core.
* Email: [email protected] / maizer.gomes@ekutivasolutions / [email protected]
* Usuário: maizerg
* Data: 6/10/20
* Hora: 8:11 PM
*/

namespace Sendportal\Base\Adapters;


use ElasticEmailClient\ApiConfiguration;
use ElasticEmailClient\ElasticClient;
use Illuminate\Support\Arr;
use Sendportal\Base\Services\Messages\MessageTrackingOptions;

class ElasticMailAdapter extends BaseMailAdapter
{

protected $url = 'https://api.elasticemail.com/v2/';
/**
* @var \ElasticEmailClient\ElasticClient
*/
protected $client;

/**
* @inheritDoc
*/
public function send(string $fromEmail, string $toEmail, string $subject, MessageTrackingOptions $trackingOptions, string $content): ?string
{
$result = $this->resolveClient()->Email->Send(
$subject,
$fromEmail,
//TODO Need to get fromName from the campaign
null,
$fromEmail,
null,
$fromEmail,
null,
null,
null,
[$toEmail],
[],
[],
[],
[],
[],
null,
null,
null,
$content,
null,
'utf-8',
null,
null,
null,
null,
[],
[],
null,
[],
null,
null,
null,
[]
//TODO ElasticEmail API rejects request when tracking options are set. Maybe because my account is trial.
// $trackingOptions->isOpenTracking(),
// $trackingOptions->isClickTracking()
);

return $this->resolveMessageId($result);
}

protected function resolveClient(): ElasticClient
{
if ($this->client) {
return $this->client;
}

$configuration = new ApiConfiguration([
'apiUrl' => $this->url,
'apiKey' => Arr::get($this->config, 'key'),
]);

$this->client = new ElasticClient($configuration);

return $this->client;
}

protected function resolveMessageId($result): string
{
return $result->messageid;
}
}
21 changes: 21 additions & 0 deletions src/Events/Webhooks/ElasticWebhookReceived.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Sendportal\Base\Events\Webhooks;

use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;

class ElasticWebhookReceived
{
use Dispatchable, InteractsWithSockets, SerializesModels;

/** @var array */
public $payload;

public function __construct(array $payload)
{
$this->payload = $payload;
}
}
8 changes: 5 additions & 3 deletions src/Factories/MailAdapterFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Sendportal\Base\Factories;

use Sendportal\Base\Adapters\ElasticMailAdapter;
use Sendportal\Base\Adapters\MailgunMailAdapter;
use Sendportal\Base\Adapters\PostmarkMailAdapter;
use Sendportal\Base\Adapters\SendgridMailAdapter;
Expand All @@ -17,10 +18,11 @@ class MailAdapterFactory
{
/** @var array */
public static $adapterMap = [
EmailServiceType::SES => SesMailAdapter::class,
EmailServiceType::SES => SesMailAdapter::class,
EmailServiceType::SENDGRID => SendgridMailAdapter::class,
EmailServiceType::MAILGUN => MailgunMailAdapter::class,
EmailServiceType::POSTMARK => PostmarkMailAdapter::class
EmailServiceType::MAILGUN => MailgunMailAdapter::class,
EmailServiceType::POSTMARK => PostmarkMailAdapter::class,
EmailServiceType::ELASTIC => ElasticMailAdapter::class,
];

/**
Expand Down
29 changes: 29 additions & 0 deletions src/Http/Controllers/Api/Webhooks/ElasticWebhooksController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace Sendportal\Base\Http\Controllers\Api\Webhooks;

use Illuminate\Http\Response;
use Illuminate\Support\Facades\Log;
use Sendportal\Base\Events\Webhooks\ElasticWebhookReceived;
use Sendportal\Base\Http\Controllers\Controller;

class ElasticWebhooksController extends Controller
{
public function handle(): Response
{
/** @var array $payload */
$payload = request()->only('date', 'status', 'category', 'messageid', 'target');

Log::info('ElasticEmail webhook received');

if (count($payload)) {
event(new ElasticWebhookReceived($payload));

return response('OK');
}

return response('OK (not processed');
}
}
129 changes: 129 additions & 0 deletions src/Listeners/Webhooks/HandleElasticWebhook.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<?php

declare(strict_types=1);

namespace Sendportal\Base\Listeners\Webhooks;

use Carbon\Carbon;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use RuntimeException;
use Sendportal\Base\Events\Webhooks\ElasticWebhookReceived;
use Sendportal\Base\Events\Webhooks\SendgridWebhookReceived;
use Sendportal\Base\Services\Webhooks\EmailWebhookService;

class HandleElasticWebhook implements ShouldQueue
{
/** @var string */
public $queue = 'sendportal-webhook-process';

/** @var EmailWebhookService */
private $emailWebhookService;

public function __construct(EmailWebhookService $emailWebhookService)
{
$this->emailWebhookService = $emailWebhookService;
}

public function handle(ElasticWebhookReceived $event): void
{
// https://help.elasticemail.com/en/articles/2376855-how-to-manage-http-web-notifications-webhooks
$messageId = $this->extractMessageId($event->payload);
$eventName = $this->extractEventName($event->payload);

Log::info('Processing ElasticEmail webhook.', ['type' => $eventName, 'message_id' => $messageId]);

switch ($eventName) {
case 'Sent':
$this->handleSent($messageId, $event->payload);
break;

case 'Opened':
$this->handleOpen($messageId, $event->payload);
break;

case 'Clicked':
$this->handleClick($messageId, $event->payload);
break;

case 'AbuseReport':
$this->handleAbuseReport($messageId, $event->payload);
break;

case 'Error':
$this->handleError($messageId, $event->payload);
break;

case 'Unsubscribed':
$this->handleUnsubscribe($messageId, $event->payload);
break;

default:
throw new RuntimeException("Unknown ElasticEmail webhook event type '{$eventName}'.");
}
}

private function extractMessageId(array $payload): string
{
return Arr::get($payload, 'messageid');
}

private function extractEventName(array $payload): string
{
return Arr::get($payload, 'status');
}

private function handleSent(string $messageId, array $content): void
{
$timestamp = $this->extractTimestamp($content);

$this->emailWebhookService->handleDelivery($messageId, $timestamp);
}

private function extractTimestamp($payload): Carbon
{
return Carbon::createFromDate(Arr::get($payload, 'date'));
}

private function handleOpen(string $messageId, array $content): void
{
$timestamp = $this->extractTimestamp($content);

$this->emailWebhookService->handleOpen($messageId, $timestamp, null);
}

private function handleClick(string $messageId, array $content): void
{
$url = Arr::get($content, 'target');
$timestamp = $this->extractTimestamp($content);

$this->emailWebhookService->handleClick($messageId, $timestamp, $url);
}

private function handleAbuseReport(string $messageId, array $content): void
{
$timestamp = $this->extractTimestamp($content);

$this->emailWebhookService->handleComplaint($messageId, $timestamp);
}

private function handleError(string $messageId, array $content): void
{
$timestamp = $this->extractTimestamp($content);
$description = Arr::get($content, 'category');

//TODO Create method to determine the severity of the failure:
// Ignore|Spam|BlackListed|NoMailbox|GreyListed|Throttled|Timeout|ConnectionProblem|SPFProblem|AccountProblem|DNSProblem|WhitelistingProblem|CodeError|ManualCancel|ConnectionTerminated|ContentFilter|NotDelivered|Unknown

$this->emailWebhookService->handleFailure($messageId, 'Temporary', $description, $timestamp);
}

private function handleUnsubscribe(string $messageId, array $content): void
{
$timestamp = $this->extractTimestamp($content);

$this->emailWebhookService->handleComplaint($messageId, $timestamp);
}
}
10 changes: 6 additions & 4 deletions src/Models/EmailServiceType.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@

class EmailServiceType extends BaseModel
{
public const SES = 1;
public const SES = 1;
public const SENDGRID = 2;
public const MAILGUN = 3;
public const MAILGUN = 3;
public const POSTMARK = 4;
public const ELASTIC = 5;

/** @var array */
protected static $types = [
self::SES => 'SES',
self::SES => 'SES',
self::SENDGRID => 'Sendgrid',
self::MAILGUN => 'Mailgun',
self::MAILGUN => 'Mailgun',
self::POSTMARK => 'Postmark',
self::ELASTIC => 'ElasticEmail',
];

/**
Expand Down
5 changes: 5 additions & 0 deletions src/Providers/EventServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@

use Sendportal\Base\Events\MessageDispatchEvent;
use Sendportal\Base\Events\SubscriberAddedEvent;
use Sendportal\Base\Events\Webhooks\ElasticWebhookReceived;
use Sendportal\Base\Events\Webhooks\MailgunWebhookReceived;
use Sendportal\Base\Events\Webhooks\PostmarkWebhookReceived;
use Sendportal\Base\Events\Webhooks\SendgridWebhookReceived;
use Sendportal\Base\Events\Webhooks\SesWebhookReceived;
use Sendportal\Base\Listeners\MessageDispatchHandler;
use Sendportal\Base\Listeners\Webhooks\HandleElasticWebhook;
use Sendportal\Base\Listeners\Webhooks\HandleSesWebhook;
use Sendportal\Base\Listeners\Webhooks\HandleMailgunWebhook;
use Sendportal\Base\Listeners\Webhooks\HandlePostmarkWebhook;
Expand Down Expand Up @@ -43,6 +45,9 @@ class EventServiceProvider extends ServiceProvider
SesWebhookReceived::class => [
HandleSesWebhook::class
],
ElasticWebhookReceived::class => [
HandleElasticWebhook::class
],
SubscriberAddedEvent::class => [
// ...
],
Expand Down
1 change: 1 addition & 0 deletions src/Services/QuotaService.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public function exceedsQuota(Campaign $campaign): bool
case EmailServiceType::SENDGRID:
case EmailServiceType::MAILGUN:
case EmailServiceType::POSTMARK:
case EmailServiceType::ELASTIC:
return false;
}

Expand Down
Loading