Possibly the most-requested feature from our hosts on PadPiper has been the ability to reply to messages via email. Anyone who has used Craigslist knows about this feature. Instead of going to a website or application to respond to a message, you simply reply to the email.
While it may sound as simple as POSTing to an API to create the message, making this a seamless process for the user involves a lot of moving parts.
Since we use SendGrid to send all of our emails we decided it would be best to stick with them for the inbound emails. Their documentation is limited because all that’s really done on SendGrid is to add the webhook URL. So this is a short guide to setting up Inbound Parse in a NodeJS application.
Add a new MX record to your DNS that points to SendGrid. This will allow SendGrid to intercept emails and forward them to your API. Create the record for a subdomain that you want emails to be sent to and point it to
10 mx.sendgrid.net. All emails sent to this domain will be routed through SendGrid and to your API.
For example, we used Google Domains to add the MX record for a m.padpiper.com subdomain. This means any email with the form
firstname.lastname@example.org will be routed through SendGrid. More details can be found here.
Add a webhook to Inbound Parse in SendGrid as documented here. First, click Add Host & Url in the top-right hand corner. Next, enter your desired subdomain along with your site’s domain. Be sure to use the previous domain you made in your MX record and an endpoint on your API that will receive the emails. Lastly, enter the API endpoint (Destination URL) that the email payload will be sent to.
Setup your API to receive the webhook. Make sure you correctly parse it or you won’t be able to read the contents. We use multer to parse the body, it’s added before the main body parsing in order to custom parse it.
import multer from 'multer';// SendGrid inbound emails
app.post(‘/api/inbound_emails’, multer().any(), inbound.index);//Other routes and body parsing
The last step is the trickiest, yet most important — parsing the POST you receive from SendGrid.
Parse the contents and make the necessary associated updates in your database. The below snippet provides a simplified overview of what our process looks like (stripped of error handling and method implementations).
// Get signed_key
const msgKey = getSignedKey(req.body.text);// Verify key
const keyContents = verifyKey(msgKey);// Get the message that is being responded too
const prevMessage = await db.message.findById(keyContents.id);// Validate the sender is in this conversation / allowed to respond
const verifiedSender = await verifySender(keyContents, prevMessage);// Parse message data
const emailMessage = parseEmail(req.body.text);// Validate first time (idempotent)
const date = parseMessageDate(req.body.headers);
await validateEvent(keyContents, verifiedSender.id, date);// Create message object
const newMessage = buildMessage(emailMessage, prevMessage.convo_id, verifiedSender.id);// create message on the conversation and send out notifications
createMessage(verifiedSender.id, prevMessage.convo_id, newMessage);
There are a few key points here:
Security - properly verify the sender of the message. We attach signed keys to all of our emails in order to securely associate the message and its sender.
Idempotence - only process an email once. We store every processed email with the date and contents to confirm it’s the first time we’ve received it.
Association - use a signed key to verify the user and to associate the response to the original message. This can be done by including the original message ID in the contents of the key.
Have any feedback? Send us a message!