Skip to content

Commit

Permalink
ESP Webhooks
Browse files Browse the repository at this point in the history
Adds an endpoint for receiving Webhooks from Campaign Monitor.

Fixes #225
  • Loading branch information
stephenyeargin committed Mar 12, 2021
1 parent 9f573f9 commit d841839
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 23 deletions.
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,4 @@ USPS_PASSWORD=

CAMPAIGN_MONITOR_API_KEY=
CAMPAIGN_MONITOR_DEFAULT_LIST_ID=
CAMPAIGN_MONITOR_WEBHOOK_AUTH_KEY=
1 change: 1 addition & 0 deletions config/packages/campaign_monitor.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
parameters:
campaign_monitor.api_key: '%env(CAMPAIGN_MONITOR_API_KEY)%'
campaign_monitor.default_list_id: '%env(CAMPAIGN_MONITOR_DEFAULT_LIST_ID)%'
campaign_monitor.webhook_token: '%env(CAMPAIGN_MONITOR_WEBHOOK_TOKEN)%'
23 changes: 3 additions & 20 deletions src/Controller/UpdateController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@

namespace App\Controller;

use App\Service\EmailService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Mime\Header\Headers;
use Symfony\Component\Mailer\MailerInterface;

use App\Entity\Member;
use App\Form\MemberUpdateType;
Expand Down Expand Up @@ -35,7 +33,7 @@ public function updateFromQueryString(Request $request)
/**
* @Route("/update-my-info/{externalIdentifier}/{updateToken}", name="self_service_update")
*/
public function update(Request $request, MailerInterface $mailer)
public function update(Request $request, EmailService $emailService)
{
$member = $this->getDoctrine()->getRepository(Member::class)->findOneBy([
'externalIdentifier' => $request->get('externalIdentifier')
Expand All @@ -55,25 +53,10 @@ public function update(Request $request, MailerInterface $mailer)
$member = $form->getData();
// If form is submitted, member is no longer "lost"
$member->setIsLost(false);
// Set headers for grouping in transactional email reporting
$headers = new Headers();
$headers->addTextHeader('X-Cmail-GroupName', 'Member Record Update');
$headers->addTextHeader('X-MC-Tags', 'Member Record Update');
$message = new TemplatedEmail($headers);
$message
->to($this->getParameter('app.email.to'))
->from($this->getParameter('app.email.from'))
->subject(sprintf('Member Record Update: %s', $member->getDisplayName()))
->htmlTemplate('update/email_update.html.twig')
->context(['member' => $member])
;
if ($member->getPrimaryEmail()) {
$message->replyTo($member->getPrimaryEmail());
}
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($member);
$entityManager->flush();
$mailer->send($message);
$emailService->sendMemberUpdate($member);
return $this->render('update/confirmation.html.twig');
}

Expand Down
69 changes: 69 additions & 0 deletions src/Controller/WebhookController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

namespace App\Controller;

use App\Service\EmailService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class WebhookController extends AbstractController
{
/**
* @Route("/webhook", name="webhook")
*/
public function index(Request $request): Response
{
return $this->json([
'status' => 200,
'title' => 'Success',
'message' => 'Webhooks are available.'
]);
}

/**
* @Route("/webhook/email-service", name="webhook_email_service", methods={"POST"})
*/
public function emailServiceWebhook(Request $request, EmailService $emailService): Response
{
// Fail if not configured
if (!$emailService->isConfigured()) {
return $this->json([
'status' => 500,
'title' => 'Internal server error',
'details' => 'Email service not configured.'
], 500);
}

// Fail if token is missing or mismatched
if (!$request->get('token') ||
$request->get('token') != $emailService->getWebhookToken()
) {
return $this->json([
'status' => 403,
'title' => 'Access denied',
'details' => 'Invalid credentials.'
], 403);
}

// Process payload
try {
$output = $emailService->processWebhookBody($request->getContent());
return $this->json([
'status' => 200,
'title' => 'success',
'details' => 'Processed webhook.',
'extra' => $output
]);
} catch (\Exception $e) {
return $this->json([
'status' => 'error',
'title' => 'Internal server error',
'message' => $e->getMessage()
], 500);
}
}


}
91 changes: 88 additions & 3 deletions src/Service/EmailService.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@

namespace App\Service;

use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use CS_REST_Subscribers;
use CS_REST_Campaigns;
use CS_REST_Subscribers;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Header\Headers;

use App\Entity\Member;

Expand All @@ -16,15 +20,22 @@ class EmailService

protected $defaultListId;

protected $webhookToken;

protected $client;

protected $campaignsClient;

public function __construct(ParameterBagInterface $params)
protected $em;

protected $mailer;

public function __construct(ParameterBagInterface $params, EntityManagerInterface $em, MailerInterface $mailer)
{
$this->params = $params;
$this->apiKey = $params->get('campaign_monitor.api_key');
$this->defaultListId = $params->get('campaign_monitor.default_list_id');
$this->webhookToken = $params->get('campaign_monitor.webhook_token');
$this->client = new CS_REST_Subscribers(
$this->defaultListId,
[
Expand All @@ -37,6 +48,8 @@ public function __construct(ParameterBagInterface $params)
'api_key' => $this->apiKey
]
);
$this->em = $em;
$this->mailer = $mailer;
}

public function isConfigured(): bool
Expand Down Expand Up @@ -138,6 +151,78 @@ public function getCampaignById($campaignId): object
return $result->response;
}

public function getWebhookToken(): string
{
return $this->webhookToken;
}

public function processWebhookBody(string $content): array
{
$content = json_decode($content, null, $depth=512, JSON_THROW_ON_ERROR);
if (!property_exists($content, 'Events') || !is_array($content->Events)) {
throw new \Exception('Invalid webhook payload. Must have Events.');
}
$memberRepository = $this->em->getRepository(Member::class);
$output = [];
foreach ($content->Events as $event) {
switch($event->Type) {
case 'Update':
$member = $memberRepository->findOneBy([
'primaryEmail' => $event->OldEmailAddress
]);
if (!$member) {
$output[] = [
'result' => sprintf(
'Unable to locate member with %s',
$event->OldEmailAddress
),
'payload' => $event
];
break;
}
$member->setPrimaryEmail($event->EmailAddress);
$this->sendMemberUpdate($member);
$this->em->persist($member);
$this->em->flush();
$output[] = [
'result' => sprintf(
'Email for %s updated from %s to %s',
$member,
$event->OldEmailAddress,
$event->EmailAddress
),
'payload' => $event
];
break;
default:
$output[] = [
'result' => 'No action taken.',
'payload' => $event
];
}
}
return $output;
}

public function sendMemberUpdate(Member $member): void
{
$headers = new Headers();
$headers->addTextHeader('X-Cmail-GroupName', 'Member Record Update');
$headers->addTextHeader('X-MC-Tags', 'Member Record Update');
$message = new TemplatedEmail($headers);
$message
->to($this->params->get('app.email.to'))
->from($this->params->get('app.email.from'))
->subject(sprintf('Member Record Update: %s', $member->getDisplayName()))
->htmlTemplate('update/email_update.html.twig')
->context(['member' => $member])
;
if ($member->getPrimaryEmail()) {
$message->replyTo($member->getPrimaryEmail());
}
$this->mailer->send($message);
}

/* Private Methods */

private function buildCustomFieldArray(Member $member): array
Expand Down

0 comments on commit d841839

Please sign in to comment.