Digging into Mailgun’s API with PHP

Mailgun is a popular transactional email service which I use on many WordPress websites. They have an official WordPress plugin and documentation for developers. I spent the weekend digging into their API in order to revamp an integration between Mailgun and a WordPress plugin I wrote. Interested to see what it looks like to integrate with Mailgun? Then read on.

Start by adding Mailgun’s PHP client to a custom WordPress plugin via Composer

Since my WordPress plugin, CaptainCore, is already using Composer for autoloading, adding Mailgun’s PHP client is done with a single composer require command.

cd wp-content/plugins/captaincore/
composer require mailgun/mailgun-php kriswallsmith/buzz nyholm/psr7

Next I recommend defining a MAILGUN_API_KEY constant within wp-config.php.

define( 'MAILGUN_API_KEY', "key-########################" );

Mailgun’s documentation is unfortunately is a bit outdated mainly showing their v2 code. The new v3 PHP client can be started using this format.

$mailgun = \Mailgun\Mailgun::create( MAILGUN_API_KEY );

The Mailgun integration I created fetches the last 30 days of events and displays them on the frontend using a Vuetify datatable shown below.

For this type of request I only need a few details from Mailgun’s events endpoint. That required reading source code for Mailgun’s event model and creating the following PHP function which produces just the right data that I want to pass to my Javascript frontend.

function mailgun_events( $mailgun_subdomain ) {

	// Prep to handle remote responses
	$response = [];

	// Load Mailgun API client
	$mailgun = \Mailgun\Mailgun::create( MAILGUN_API_KEY );

	$queryString = [
		'event' => 'accepted OR rejected OR delivered OR failed OR complained',
		'limit' => 300,
	];

	$results = $mailgun->events()->get( "$mailgun_subdomain", $queryString );
	foreach ( $results->getItems() as $item ) {
		$description = $item->getRecipient();
		if ( $item->getMessage()["headers"]["from"] ) {
			$from        = $item->getMessage()["headers"]["from"];
			$description = "{$from} -> {$description}";
		}

		$response[] = [
			"timestamp"   => $item->getTimestamp(),
			"event"       => $item->getEvent(),
			"description" => $description,
			"message"     => $item->getMessage(),
		];
	}

	return $response;

}

Mailgun’s pagination is crude and requires creative workarounds

The above code will fetch 300 records, the maximum amount allowed by Mailgun’s API. You might be wondering, as I did, how do you fetch the next page of records? Well… after crawling around the docs, there doesn’t seem to be an easy way to do that.

That’s not to say Mailgun doesn’t provide a method for paging between results. It does. Within the above $results Mailgun provides direct API links for fetching the next, previous, first and last pages. The thing is there isn’t a built-in way to use these links within API.

Format of pagination links returned from Mailgun’s API

To use these links we can directly interact with Mailgun’s API using WordPress’ built in wp_remote_get. The information is a bit scattered but through some trial and error I was able to match the output to the initial request made by Mailgun’s PHP client.

$results = wp_remote_get( $page, [ 
    "headers" => [ "Authorization" => "Basic " . base64_encode ( "api:" . MAILGUN_API_KEY ) ],
] );
$results = json_decode( $results['body'] );
foreach ( $results->items as $item ) {
    $description = $item->recipient;
    if ( $item->message->headers->from ) {
        $from        = $item->message->headers->from;
        $description = "{$from} -> {$description}";
    }
    $response->items[] = [
        "timestamp"   => $item->timestamp,
        "event"       => $item->event,
        "description" => $description,
        "message"     => $item->message,
    ];
    $response->pagination["next"]     = $results->paging->next;
}
return $response;

Putting it all together.

The final PHP I came up with will take in a Mailgun subdomain and optional page mailgun_events( $mailgun_subdomain, $page = "" ). With this, I can have my frontend Javascript code make the initial request and when needed, fetch in more records by passing in next page urls.

function mailgun_events( $mailgun_subdomain, $page = "" ) {

	// Prep to handle remote responses
	$response             = (object) [];
	$response->items      = [];
	$response->pagination = [];

	// Load Mailgun API client
	$mailgun = \Mailgun\Mailgun::create( MAILGUN_API_KEY );

	$queryString = [
		'event' => 'accepted OR rejected OR delivered OR failed OR complained',
		'limit' => 300,
	];

	if ( $page != "" ) {
		// Fetch all domains from Mailgun
		$results = wp_remote_get( $page, [ 
			"headers" => [ "Authorization" => "Basic " . base64_encode ( "api:" . MAILGUN_API_KEY ) ],
		] );
		$results = json_decode( $results['body'] );
		foreach ( $results->items as $item ) {
			$description = $item->recipient;
			if ( $item->message->headers->from ) {
				$from        = $item->message->headers->from;
				$description = "{$from} -> {$description}";
			}
			$response->items[] = [
				"timestamp"   => $item->timestamp,
				"event"       => $item->event,
				"description" => $description,
				"message"     => $item->message,
			];
			$response->pagination["next"]     = $results->paging->next;
		}
		return $response;
	}

	$results = $mailgun->events()->get( "$mailgun_subdomain", $queryString );
	foreach ( $results->getItems() as $item ) {
		$description = $item->getRecipient();
		if ( $item->getMessage()["headers"]["from"] ) {
			$from        = $item->getMessage()["headers"]["from"];
			$description = "{$from} -> {$description}";
		}

		$response->items[] = [
			"timestamp"   => $item->getTimestamp(),
			"event"       => $item->getEvent(),
			"description" => $description,
			"message"     => $item->getMessage(),
		];
	}
	$response->pagination["next"]     = $results->getNextUrl();

	return $response;

}