Skip to main content

By Jesse Schutt

Laravel as an Intermediary

If you are anything like me, you view new projects as an opportunity to stretch your abilities, tackle a challenge, and improve your coding.

This blog entry is an attempt to share a few items that stood out to me on one of Zaengle's Laravel integrations. Hopefully this will stimulate your thinking as you find creative solutions for similar problems!

Project requirements

One of our clients recently came to us with the following workflow they'd like the Zaengle team to implement for them:

  1. They wanted to compose a blog entry in their CMS.
  2. Upon publishing of the entry, they wanted the content of the blog entry to be emailed to a filtered group of their customer database (stored in Marketo).
  3. Finally, they wanted to be able to track email metrics from within their customer database.

Prerequisites

  • Customer/Lead data is stored in System A
  • Emails should be processed with System B
  • All of this should be automated
  • Transactional data should be persisted

Our solution

After brainstorming with the team and client, we decided that since there were at least 3 different systems in play (CMS, Customer Database, & Mail Processor), we needed to write a custom application that would bring all of them together. (Hence the Laravel as an Intermediary title to this blog post!)

Since all customer information lives in System A, when the client wanted to send an email blast out to a particular segment of customers, a request was made to our Laravel application (let's call it the "Intermediary App"). The Intermediary App would then acquire the necessary customer data from System A, assemble it into a campaign, and pass it off to System B, the mailing service.

As customers would interact with the emails, System B would send webhooks back to the Laravel application, which would be logged, and then sent on to the customer database to be associated with the customer records.


Laravel as an Intermediary Diagram


Benefits

There are at least three significant benefits in using Laravel as an intermediary in this situation:

  1. We are able to set up automated tests that verify data coming in and data going out.
  2. We are able to log requests and responses.
  3. We are able to transform request formats from System A intended for System B, and vice versa.

Testing

Once we injected Laravel into the flow of data between System A and System B, we were able to write tests to verify that data was being sent in the right format, and ensure it was being acted upon properly.

Handling input

We wrote a variety of tests to cover data that we expect to come into the Intermediary App. For example, when the client would write a blog post, it was necessary to have a particular set of filters in order to know who should receive an email.

/** @test */
public function it_requires_some_filters_to_send_a_campaign()
{
    $this->post('api/v1/create/campaign', [
        'state'  => null,
        'region' => null
    ])->seeJson([
        'success' => false,
        'message' => 'No filtering provided'
    ]);
}

As you can see from the test above, if a POST request is made to the api/v1/create/campaign endpoint WITHOUT any filters included, we expect to see an error that includes the message No filtering provided.

In order to send the email campaign, we need to have a few required parameters, such as a senderName, senderEmail, and emailBody. Here is a test that ensures those items are passed:

/** @test */
public function it_requires_email_details_to_send_a_campaign()
{
    $this->post('api/v1/create/campaign', [
        'state' => 'AZ',
        'region' => 'Northwest'
    ])->seeJson([
        'success' => false,
        'message' => 'Missing at least one required parameter: senderName, senderEmail, emailbody'
    ]);
}

Verifying output

"Don't Mock What You Don't Own" is a common mantra when testing systems. What that means is that we should be careful simulating the responses from external services in our tests.*

Why wouldn't we just mock the Mail Processor and call it a day? Think about it for a second. If I simulate the response from a service I don't control, what happens if the third-party service changes something on their end? My tests wouldn't be aware of it and they would still pass as normal. The only way to be absolutely certain the emails send would be to actually attempt to send them.

So, in this situation we decided on a two-pronged approach: 1) Test as much of the outgoing data as possible to be sure it matches with the mailer's specs, and 2) periodically run manual tests that hit the mailing service with dummy email(s).

Here is a test that verifies the to addresses are assembled according to the mailing service's requirements.

/** @test */
public function it_assembles_string_of_names_and_emails_for_mailer()
{
    $leads[] = factory(\App\Lead::class)->make([
        'first_name' => 'Testy',
        'last_name'  => 'McTester',
        'email'      => '[email protected]'
    ])->toArray();

    $leads[] = factory(\App\Lead::class)->make([
        'first_name' => 'Sample',
        'last_name'  => 'McSampler',
        'email'      => '[email protected]'
    ])->toArray();
    
    $transport = new MailerTransport();
    $to = $transport->assembleNamesAndEmails($leads);
    
    $this->assertSame('Testy McTester <[email protected]>, Sample McSampler <[email protected]>', $to);
}

Logging

One of the strongest benefits of using Laravel in this fashion is that we can easily store logs to track when actions take place. None of the systems talk to each other without passing through the Intermediary App, so we wrote it to create records whenever data moves through.

The app logs when a new campaign is requested from the CMS. It logs when new customer data is assembled into a mailing list. It logs when a campaign is successfully delivered. It even logs every time a webhook is sent from the mailing service back to the Intermediary App.

Yes, that's a lot of logs... But they are helpful logs! It's easy to track down issues because the logs tell us exactly what has taken place.

Each of the logs contain at least two items: json_encode(Request) & json_encode(Response). There are other bits of data tracked, but at a minimum we can see what an external system requested, and what the response was.

Conclusion

As you can see, using Laravel as an intermediary, we are able to integrate several external systems together in a way that allows us to share data, log actions, and automate workflow.

If you have any feedback, or questions, or see how we could improve this article, please ping me on twitter at @jesseschutt. Thanks!

Want to read more tips and insights on working with a Laravel development team that wants to help your organization grow for good? Sign up for our bimonthly newsletter.

By Jesse Schutt

Director of Engineering

Jesse is our resident woodworker. His signature is to find the deeper meaning in a project and the right tool for the job.