Email Robot Handing DNS Updates

I know we’ve all been there. Staring at the endless stream of new emails wondering, “Can I automate this somehow?”. Trust me, I know. That answer is always yes, it can be automated. However, the real question is whether it should be automated. Automating something takes time. It’s always a balancing act between whether spending the time now will actually save time later.

A few weeks back, due to changes with how Kinsta and Cloudflare’s wildcard SSLs work, I started receiving a flood of required DNS changes relating to SSL renewal for Anchor Hosting customers. It’s an unfortunate situation as most customers don’t need wildcard SSLs however a non-wildcard SSL is not currently an option with Kinsta. I plan to switch to non-wildcard SSLs as soon as I can which will eliminate the need to manually verify SSLs each year. In the meantime, I have a mess of SSL renewals to deal with.

I spent part of a day seeing if I could write a robot using my email client Missive and my DNS provider Constellix. The end result was a fully functional robot that takes in emails from Kinsta and actually performs the DNS updates. This is will be an overview of what it looks like to build an email robot. It’s surprisingly easier than I thought it would be.

Why Missive is awesome

While my email provider is Google Workspace, I don’t use the Gmail interface. I instead use Missive which allows me to connect all of my email accounts and business SMS into a single place. Each customer email thread can then be collaborated on privately with the Anchor Hosting team and/or assigned to someone to work on. Missive allows a team of people to run a business over email. It’s an amazing tool, more than just a better email experience.

Missive rules combined with custom webhooks unlock truly limitless possibilities.

The email robot is really just a Missive rule targeting certain emails. It applies a label and triggers a custom webhook. Here is what that looks like for my SSL renewals with Kinsta.

We can also create a second Missive rule to manually trigger the webhook whenever we add a certain label. This is great if we need to retry the webhook.

The webhook is powered by a custom WordPress REST endpoint, code shown below. My WordPress site anchor.host listens for authenticated requests from Missive. WordPress then asks Missive’s API for the contents of the email and then runs that through a regular expression pattern match to extract relevant information like the domain and TXT verification record. Then WordPress makes an API call to my domain provider Constellix to insert the new TXT record. Lastly, WordPress will respond back to Missive’s API with the results of whatever action was taken place which is added as a private note alongside of the original email.

<?php
add_action( 'rest_api_init', 'captaincore_register_rest_endpoints' );

function captaincore_register_rest_endpoints() {

    register_rest_route(
		'captaincore/v1', '/missive', [
			'methods'       => 'POST',
			'callback'      => 'captaincore_missive_func',
			'show_in_index' => false
		]
	);

};

function captaincore_missive_func( WP_REST_Request $request ) {

	$key        = $request->get_header('X-Hook-Signature');
	
	if ( empty( $key ) ) {
		return "Bad Request";
	}

	$computed_signature = 'sha256=' . hash_hmac( "sha256", $request->get_body(), CAPTAINCORE_MISSIVE_API );
	if ( ! hash_equals( $computed_signature, $key ) ) {
		return "Bad Request";
	}

	$missive    = json_decode( $request->get_body() );
	$message_id = $missive->latest_message->id;
	$message    = missive_api_get( "messages/$message_id")->messages->body;

	preg_match('/The TXT record to validate your SSL certificate renewal is:<\/p><p>(.+?)<br>(.+?)<\/p>/', $message, $matches );
	$domain     = $matches[1];
	$txt_record = $matches[2];
	$response   = ( new CaptainCore\Domains )->add_verification_record( $domain, $txt_record );

	missive_api_post( "posts", [ "posts" => [ 
		"conversation"  => $missive->conversation->id,
		"notification"  => [ "title" => "", "body" => "" ],
		"username"      => "CaptainCore Bot", 
		"username_icon" => "https://captaincore.io/logo.png",
		"markdown"      => $response
	] ] );

	return;
}

The response being posted back to Missive can even handle markdown. That’s extremely powerful and makes it possible to embed links or images. Here are examples of the possible responses I programed into my little robot:

  • Domain <domain> already has <txt-verification-record> added.
  • Domain <domain> not found with DNS provider. Nameservers are <ns1> <ns2>.
  • Added <txt-verification-record> to <domain>.

For talking to Missive’s API I wrote a quick wrapper with missive_api_get and missive_api_post functions. Here is what they look like.

<?php

/**
 * Missive API WordPress Wrapper
 *
 * @author   Austin Ginder
 */

function missive_api_get( $command ) {

	$args = [
		'timeout' => 120,
		'headers' => [
			'Content-type'  => 'application/json',
			'Authorization' => 'Bearer ' . MISSIVE_API_KEY,
		],
	];

	$remote = wp_remote_get( "https://public.missiveapp.com/v1/$command", $args );

	if ( is_wp_error( $remote ) ) {
		return $remote->get_error_message();
	} else {
		return json_decode( $remote['body'] );
	}

}

function missive_api_post( $command, $post ) {

	$args = [
		'timeout' => 120,
		'headers' => [
			'Content-type'  => 'application/json',
			'Authorization' => 'Bearer ' . MISSIVE_API_KEY,
		],
		'body'    => json_encode( $post ),
		'method'  => 'POST',
	];
	$remote    = wp_remote_post( "https://public.missiveapp.com/v1/$command/", $args );

	if ( is_wp_error( $remote ) ) {
		return $remote->get_error_message();
	} else {
		return json_decode( $remote['body'] );
	}

}

Missive rule -> webhook = automate any email

In theory, this same workflow can handle virtually any automated task when an email is received. You can imagine some crazy chain reactions all triggered by new emails. While I don’t plan on automating my entire inbox, I am now aware of what’s possible and am will continue to think about ways I can increase email efficiency. Next time I see a repetitive task that I can automate, I will.