Add a static map to an email receipt, part 2: back-end geocoding
This series of tutorials teaches you how to use the Mapbox Static Images API and the Mapbox Geocoding API to add a customized static map to an email receipt.- Part 1: Front-end template- Part 2: Back-end geocoding
Adding location context, like receipt maps, to transaction emails helps customers quickly recognize where a transaction occurred, decreasing payment disputes. Static maps from the Mapbox Static Images API are a lightweight, email-friendly way to add location context to email receipts.
Static maps need a pair of latitude and longitude coordinates. This tutorial uses the Mapbox Geocoding API to geocode merchant addresses for a static map in an email receipt. Since we can’t use front-end JavaScript in emails to geocode addresses, we’ll write Node.js code to geocode merchant addresses before sending emails using the Twilio SendGrid Mail Send API.
Part 2 of this tutorial series does not cover email receipt template design and static map customization. See Part 1 of this tutorial for the front-end code to create static maps for email receipts.
Getting started
To complete this tutorial, you will need:
- A Mapbox account and access token. Sign up for an account at mapbox.com/signup. You can find your access tokens on your account page.
- Node.js. This tutorial uses Node.js to make API calls to the Mapbox Geocoding API and Twilio SendGrid Mail Send API. This tutorial requires Node version 14 or higher.
- A Twilio SendGrid account, verified sender identity, and API key. Sign up for a Twilio SendGrid account at signup.sendgrid.com and follow the steps in Twilio’s Email API Quickstart for Node.js guide. While this tutorial uses Twilio SendGrid to send receipt emails, you are welcome to use your email delivery system of choice.
- Starter code. This guide uses an HTML email template with sample transaction JSON data that you will copy and paste into Twilio SendGrid.
Project set up
Our restaurant transaction JSON data only includes a merchant address (476 K St NW
). We need latitude and longitude coordinates for our Mapbox Static Images API call. We will use the Mapbox Geocoding API to convert our merchant address into a latitude and longitude coordinate pair ([-77.0187, 38.90227]
).
Before we write any code to geocode addresses, we need to set up our project environment.
While this tutorial uses Node.js, you are welcome to rewrite this tutorial in your back-end language of choice.
- Create a new folder for this project called receipt-map:
mkdir receipt-map
- Switch to your new project folder:
cd receipt-map
- If you’re using Node version 14, run
npm init
to create a newpackage.json
file. Use any values for the question prompts. If you’re using Node version 16 or higher, you can skip this step. - Install got, which we will use to make HTTP requests to the Mapbox Geocoding API:
npm install --save got
- Install the SendGrid helper library npm package:
npm install --save @sendgrid/mail
. We will use this package later in the tutorial. - Add
"type": "module"
, abovedependencies
in yourpackage.json
file. We need this to import the got library, which is a native ECMAScript module (ESM). If you’re using Node v16 or higher yourpackage.json
file should look like this, but with potentially different version numbers:
{
"type": "module",
"dependencies": {
"@sendgrid/mail": "^7.6.2",
"got": "^12.0.4"
}
}
- Create a new environment variable in your shell called
MAPBOX_ACCESS_TOKEN
:export MAPBOX_access_token=YOUR_MAPBOX_ACCESS_TOKEN
- Create a new environment variable in your shell called
SENDGRID_API_KEY
:export SENDGRID_API_KEY=YOUR_SENDGRID_API_KEY
- Copy
transaction.json
from the starter code zip file into yourreceipt-map
folder. - Create a new file called
email.js
:touch email.js
- Add the following code to your
email.js
file:
import got from 'got';
import sendgrid from '@sendgrid/mail';
import { createRequire } from 'module';
const mapboxToken = process.env.MAPBOX_ACCESS_TOKEN;
sendgrid.setApiKey(process.env.SENDGRID_API_KEY);
const require = createRequire(import.meta.url);
const transaction = require('./transaction.json');
This code imports dependencies, requires sample transaction JSON data, and sets access tokens and API keys for the Mapbox Geocoding API and Twilio SendGrid Mail Send API.
Geocode merchant addresses
The syntax for the Mapbox forward geocoding endpoint is https://api.mapbox.com/geocoding/v5/{endpoint}/{search_text}.json?access_token={access_token}
. A successful Mapbox Geocoding API call requires at least three parameters:
- Endpoint: Since we’re displaying results on a Mapbox map and not permanently storing results, we’ll use
mapbox.places
for{endpoint}
. - Search text: The
{search_text}
is the address of the restaurant, stored intransaction.merchant_data.address
. - Access token: The Mapbox Geocoding API requires a public access token for billing. We can store our access token in the
mapboxToken
constant and add it to the end of our API request viaaccess_token=${mapboxToken}
. While public access tokens are not sensitive, we’ll store the Mapbox access token in our environment inprocess.env.MAPBOX_ACCESS_TOKEN
as a best practice anyway to avoid hard coding tokens.
We can also add a few more optional parameters to further optimize our Geocoding API calls for the email receipt map use case:
- Types: We are only geocoding merchant addresses, not other search data types. We can add
types=address
to improve the relevancy of results. - Limit: We’re only interested in the first result from the Mapbox Geocoding API. We can add
limit=1
to our request to decrease the size of network requests and improve performance. - Country: Our sample payment processor only operates in the United States. We can add
country=US
to improve the relevancy of results. - Autocomplete: Our users aren’t typing addresses into an interactive geocoder control like mapbox-gl-geocoder. We can disable autocomplete with
autocomplete=false
to improve the accuracy of results.
Add the following code to your email.js
file:
async function geocodeAddress(transaction) {
const geocodeURL = `https://api.mapbox.com/geocoding/v5/mapbox.places/${transaction.merchant_data.address}.json?types=address&limit=1&country=US&autocomplete=false&access_token=${mapboxToken}`;
const geocodingResults = await got(geocodeURL).json();
const coordinates = geocodingResults.features[0].geometry.coordinates;
return { longitude: coordinates[0], latitude: coordinates[1] };
}
The asynchronous geocodeAddress
function uses got to forward geocode merchant addresses. Requests to the forward geocoding endpoint return an array of features, and we want the first result (geocodingResults.features[0]
). We want the latitude and longitude coordinate pair stored in geometry.coordinates
. Finally, we return the coordinate pair as an object. The two separate longitude and latitude properties are easier to use in Handlebars in Twilio email templates than a single array.
A second function called addCoordinates
updates the transaction
object to include the geocoded coordinates. Since we want to chain multiple functions together, addCoordinates
returns a promise. Add this code to the bottom of your email.js
file:
function addCoordinates(transaction, coordinates) {
const geocodedTransaction = transaction;
geocodedTransaction.merchant_data.coordinates = coordinates;
return Promise.resolve(geocodedTransaction);
}
Finally, we run the asynchronous geocodeTransaction
function, passing the promise (coordinates
) to the addCoordinates
function. Add this code to the bottom of your email.js
file:
const geocodeTransaction = geocodeAddress(transaction);
geocodeTransaction
.then((coordinates) => addCoordinates(transaction, coordinates))
.then((geocodedTransaction) => console.log(geocodedTransaction));
Your email.js
file should look like this:
import got from 'got';
import sendgrid from '@sendgrid/mail';
import { createRequire } from 'module';
const mapboxToken = process.env.MAPBOX_ACCESS_TOKEN;
sendgrid.setApiKey(process.env.SENDGRID_API_KEY);
const require = createRequire(import.meta.url);
const transaction = require('./transaction.json');
async function geocodeAddress(transaction) {
const geocodeURL = `https://api.mapbox.com/geocoding/v5/mapbox.places/${transaction.merchant_data.address}.json?types=address&limit=1&country=US&autocomplete=false&access_token=${mapboxToken}`;
const geocodingResults = await got(geocodeURL).json();
const coordinates = geocodingResults.features[0].geometry.coordinates;
return { longitude: coordinates[0], latitude: coordinates[1] };
}
function addCoordinates(transaction, coordinates) {
const geocodedTransaction = transaction;
geocodedTransaction.merchant_data.coordinates = coordinates;
return Promise.resolve(geocodedTransaction);
}
const geocodeTransaction = geocodeAddress(transaction);
geocodeTransaction
.then((coordinates) => addCoordinates(transaction, coordinates))
.then((geocodedTransaction) => console.log(geocodedTransaction));
Run node email.js
. You should see the transaction JSON data in the console but with a new coordinates
property with a value of { longitude: -77.0187, latitude: 38.90227 }
.
Create an email template
We need to create an email template to send emails with Twilio SendGrid. The template.html
file from the starter code zip file contains the email receipt template we created in Part 1 of this tutorial series.
Follow the steps below to create a dynamic transactional template in Twilio SendGrid:
- Download the starter code zip file from the Getting Started section.
- Unzip the zip file.
- Open
template.html
andtransaction.json
from the zip file in a text editor. - Login to Twilio SendGrid in a web browser.
- Click on Email API.
- Click on Dynamic Templates.
- Click on Create a Dynamic Template.
- Type Receipt for the Dynamic Template Name.
- Click Create.
- Click on Receipt under Template.
- Click on Add Version.
- Select Blank Template.
- Select the Code Editor option.
- Click on Test Data.
- Copy and paste
transaction.json
into the Test Data panel. - Click on Code.
- Delete the pre-populated HTML code in the Code panel.
- Copy and paste
template.html
into the Code panel. Your email receipt template should not have a static map yet.
- Replace
YOUR_MAPBOX_ACCESS_TOKEN
in line 126 with your Mapbox access token. - Click on Settings.
- Type v1 under Version Name.
- Type Your receipt for the Subject.
- Click on Save.
- Click on the left arrow in the top left-hand corner to exit the template editor.
- Click on Receipt.
- You should see the template ID in the menu. Copy this value and save it for the next step.
Send emails
Now that we have a template in Twilio SendGrid, we can use our transaction data - with geocoded coordinates - as dynamic template data.
Add the following code after line 24 of your email.js
file. Make sure to update the code with your testing email address, verified sender email address, and dynamic template ID.
function sendEmail(transaction) {
const message = {
to: 'customer@example.com', // replace with your testing email address
from: 'sender@example.com', // replace with your verified sender email address
templateId: 'd-bee9c2f190964fc6b7d5e9c1ea8b9cb4', // replace with your template ID
dynamicTemplateData: transaction
};
sendgrid
.send(message)
.then((response) => {
console.log(response[0].statusCode);
console.log(response[0].headers);
})
.catch((error) => {
console.error(error);
});
}
Finally, to run all three functions in the correct order (geocode, update transaction object, then send an email with the updated transaction object), replace lines 47-49 with the following code:
geocodeTransaction
.then((coordinates) => addCoordinates(transaction, coordinates))
.then((geocodedTransaction) => sendEmail(geocodedTransaction));
Run node email.js
to send your email receipt with a static map. You should see logs for a 202 status code and headers in the console. You should also receive your test email with the subject “Your Receipt” in the inbox of your testing email address.
Final product
In part 1 of this tutorial series, you built an email receipt for a restaurant transaction with a static map using the Mapbox Static Images API. In part 2, you used the Mapbox Geocoding API to geocode merchant addresses for static maps in email receipts.
import got from 'got';
import sendgrid from '@sendgrid/mail';
import { createRequire } from 'module';
const mapboxToken = process.env.MAPBOX_ACCESS_TOKEN;
sendgrid.setApiKey(process.env.SENDGRID_API_KEY);
const require = createRequire(import.meta.url);
const transaction = require('./transaction.json');
async function geocodeAddress(transaction) {
const geocodeURL = `https://api.mapbox.com/geocoding/v5/mapbox.places/${transaction.merchant_data.address}.json?limit=1&country=US&autocomplete=false&access_token=${mapboxToken}`;
const geocodingResults = await got(geocodeURL).json();
const coordinates = geocodingResults.features[0].geometry.coordinates;
return { longitude: coordinates[0], latitude: coordinates[1] };
}
function addCoordinates(transaction, coordinates) {
const geocodedTransaction = transaction;
geocodedTransaction.merchant_data.coordinates = coordinates;
return Promise.resolve(geocodedTransaction);
}
function sendEmail(transaction) {
const message = {
to: 'customer@example.com', // replace with your testing email address
from: 'sender@example.com', // replace with your verified sender email address
templateId: 'd-bee9c2f190964fc6b7d5e9c1ea8b9cb4', // replace with your template ID
dynamicTemplateData: transaction
};
sendgrid
.send(message)
.then((response) => {
console.log(response[0].statusCode);
console.log(response[0].headers);
})
.catch((error) => {
console.error(error);
});
}
const geocodeTransaction = geocodeAddress(transaction);
geocodeTransaction
.then((coordinates) => addCoordinates(transaction, coordinates))
.then((geocodedTransaction) => sendEmail(geocodedTransaction));
Next steps
Learn how to customize your receipt map further and build for finance or retail use cases.
- Learn how to create a customized receipt map in part 1 of this tutorial series.
- Practice creating Geocoding API requests in the Geocoding API Playground.
- Create a custom style for your receipt map.
- Use a custom icon for your receipt map.
- Learn more about Mapbox’s solutions for the retail industry, including address autofill.
- Read the build a store locator using Mapbox GL JS tutorial and learn how to sort stores by distance.
- Learn more about dynamically adding custom styles to a static map by using style parameters with the Static Images API.
- Watch the add a gradient route line to a static map video to add route lines to a static map, useful for transportation and rideshare email receipts.