Add a static map to an email receipt, part 1: front-end template
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
This tutorial uses the Mapbox Static Images API and Twilio SendGrid to add a customized static map of a merchant address to an email receipt.
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.
This tutorial assumes that your transaction data for the email receipt already includes geocoded latitude and longitude coordinates. If you need to geocode merchant addresses, read part 2 of this tutorial series.
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.
- A Twilio SendGrid account. Sign up for a Twilio SendGrid account at signup.sendgrid.com. While this tutorial uses Twilio SendGrid for email templates and delivery, 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.
Create a receipt email template
In my most recent bank statement, I saw the billing descriptor TST* RICHARD SANDOVAL RES
. At first, I was confused. I then remembered I dined at the restaurant dLeña in Washington, D.C. recently. Chef Richard Sandoval owns dLeña.
Confusing billing descriptors like TST* RICHARD SANDOVAL RES
can lead to customers not recognizing financial transactions. Confused customers may open payment disputes, which increases support costs. Adding location context can help customers recognize transactions, which reduces payment disputes. Receipt maps are particularly effective as they help customers visualize where a transaction occurred.
For this tutorial, we will use my dinner at dLeña as an example of how to add location context to an email receipt.
Create an email template in Twilio SendGrid
This tutorial uses Twilio SendGrid for email templating and delivery. You can use the HTML email template and sample transaction data with your email delivery system of choice. This example with Twilio SendGrid uses the Handlebars template language. If you’re using a different email provider, you may need to convert the code samples from this tutorial from Handlebars to another template language like Liquid.
First, we will create a receipt email template in Twilio SendGrid. This template does not include a receipt map yet. To create the email 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 Design Library in the left-hand menu.
- Click the Create Email Design button.
- 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 template should look like the image below:
To verify our template works, we can send a test email directly from Twilio SendGrid:
- Click on Settings in the top left-hand corner.
- Type
Receipt
for Email Design Name. - Type
Your receipt
for Subject. - Click on Test your email.
- Type the email address for your verified sender identity in From Address.
- Enter the email address to receive the test email in Email Addresses under Send a Test Email.
- Click on the Send Test Message button.
Your test email should look like this:
Email receipt best practices
Email clients support a limited set of HTML content. As a result, our sample email template uses email best practices instead of the latest web standards. Our template uses inline styles instead of classes, tables layouts instead of <div>
, and table cellpadding instead of CSS padding. Since email client support for CSS media queries is inconsistent, our template uses a hard-coded width of 320px
. The narrow width loads well on smartphones and makes our email receipt look more like a narrow printed receipt!
Our sample template also uses receipt design best practices. We used a bold font for the restaurant name and included a restaurant logo in the header. We used a larger font size for the total amount. We de-emphasized transaction metadata by using a smaller, light gray font. Finally, we use dotted lines to separate sections of the receipt.
Add a static receipt map
Create a Mapbox Static Images API call
The syntax for the Mapbox Static Images API is https://api.mapbox.com/styles/v1/{username}/{style_id}/static/{longitude},{latitude},{zoom_level}/{image_width}x{image_height}
. A successful Static Images API call requires at least seven parameters:
- Username: The mapbox.com username that owns the map style used in your static map. If you’re using a Mapbox-designed template style like Mapbox Light or Mapbox Streets, use
mapbox
. If you’re using a custom style, this is your username. - Style ID: The style ID of the map style used in your static map. For example, if you’re using the Mapbox Streets style, the style ID is
streets-v12
. For custom styles, you can find your style ID in the styles page in Mapbox Studio. - Longitude: The longitude (a number between
-180
and180
) of the center point for the static map. For our restaurant, this is-77.0187
. - Latitude: The latitude (a number between
-85.0511
and85.0511
) of the center point for the static map. For our restaurant, this is38.90227
. - Zoom level: The zoom level of the static map. Web maps have zoom levels that range from 0 (the entire planet) to 22 (part of a building).
- Image width and height: The size of the static map image in pixels. The minimum size is
1x1
and the maximum size is1280x1280
. - Access token: Like the Mapbox Geocoding API, the Mapbox Static Images API requires a public access token for billing. This access token is not sensitive and is safe to use in front-end code. We hard-coded this token in our email template for convenience, but you could store the access token in the JSON response instead.
Here’s a Static Images API URL for our restaurant using the Mapbox Streets style. Copy and paste this link into your web browser to test out the static map:
https://api.mapbox.com/styles/v1/mapbox/streets-v12/static/-77.0187,38.90227,14/320x150?access_token=YOUR_MAPBOX_ACCESS_TOKEN
We use 320px
for the image width to match the width of the receipt table in our HTML email template. A height of 150px
is enough to see neighborhood context without overwhelming our receipt table. Finally, since we’re showing a restaurant in an urban area, we’ll use 14 for the zoom level. Zoom level 14 provides enough context of the restaurant’s intersection (5th & K St NW) and enough neighborhood context (Mt Vernon Triangle) to know where the restaurant is in Washington, D.C.
If you’re showing receipt maps for businesses in non-urban areas, you may want to use zoom levels 11-13 for suburban locations and 7-10 for rural areas. You could add information about whether the merchant is urban, suburban, or rural to the transaction JSON and then dynamically change the zoom level in the email receipt.
Visit the Static Images API Playground to test out different zoom levelsUpdate the email template
To add the static map to our email template, we’ll create a new table row above the address table row.
Copy and paste the following code after line 119 of the Code panel in Twilio SendGrid, above the {/* Address */}
comment. Replace YOUR_MAPBOX_ACCESS_TOKEN
with your Mapbox access token.
{{#if merchant_data.coordinates }}
<tr>
<td style="padding: 0">
<img
alt="{{merchant_data.name}} at {{merchant_data.address}} in {{merchant_data.city}}, {{merchant_data.state}}"
src="https://api.mapbox.com/styles/v1/mapbox/streets-v12/static/{{merchant_data.coordinates.longitude}},{{merchant_data.coordinates.latitude}},14/320x150?access_token=YOUR_MAPBOX_ACCESS_TOKEN"
/>
</td>
</tr>
{{/if}}
Your receipt email template should look like this:
The 0px
padding in the table cell ensures the map is the same width as the receipt. We also wrap the entire table row in a Handlebars if conditional block in case the back-end JSON data is missing coordinates.
Customize the receipt map
The Mapbox Streets style includes some points of interest (POIs) that we don’t need for a receipt map. It’s also unclear where the restaurant is in the static map. We can improve our receipt map implementation by changing the map style, adding a marker, adjusting attribution, and enabling high-density display support.
Map style
The Mapbox Streets style features hotel points of interest (POIs) that aren’t relevant to the receipt use case. The Mapbox Streets style might even show nearby competing restaurants!
We can swap the Mapbox Streets style (style ID streets-v12
) with either the Mapbox Light style (style ID light-v11
) or the Mapbox Dark style (style ID dark-v11
) to quickly hide all POI categories. While both map styles match our color scheme, Mapbox Dark looks better in Dark Mode. It is outside the scope of this tutorial, but we could update our email template to detect Light Mode versus Dark Mode and switch between Mapbox Light and Mapbox Dark map styles appropriately.
Update line 123 in the Code panel of the template in Twilio SendGrid to use the following updated image tag. Replace YOUR_MAPBOX_ACCESS_TOKEN
with your Mapbox access token.
<img
alt="{{merchant_data.name}} at {{merchant_data.address}} in {{merchant_data.city}}, {{merchant_data.state}}"
src="https://api.mapbox.com/styles/v1/mapbox/dark-v11/static/{{merchant_data.coordinates.longitude}},{{merchant_data.coordinates.latitude}},14/320x150?access_token=YOUR_MAPBOX_ACCESS_TOKEN"
/>
Your receipt email template should look like this:
If you need to further customize your map style - like highlighting concert venue and theater POIs but hiding competing restaurant POIs - you can create a custom style in Mapbox Studio. In the Static Images API URL, replace mapbox
with your username and replace streets-v12
or dark-v11
with the style ID for your custom style. You will need to publish your custom style before using it with the Static Images API.
Adding a marker
While our static map shows the neighborhood of our restaurant, it’s not clear where the restaurant is on the map. We can add or overlay a marker to show the exact location of the restaurant.
To add a default map marker to our Static Images API URL, add pin-l(-77.0187,38.90227)
after /static/
. This parameter will add a large pin (pin-l
) using the default pin color #7e7e7e
at longitude -77.0187
and latitude 38.90227
. Here’s our updated URL and static map with the new pin parameter:
https://api.mapbox.com/styles/v1/mapbox/dark-v11/static/pin-l(-77.0187,38.90227)/-77.0187,38.90227,14/320x150?access_token=YOUR_MAPBOX_ACCESS_TOKEN
The default dark gray map marker color (#7e7e7e
) blends in with the Mapbox Dark map style. To emphasize the marker, we can override the default value with a lighter gray hex code (#c4c4c4
). Add pin-l+c4c4c4(-77.0187,38.90227)
to your Static Images API URL:
https://api.mapbox.com/styles/v1/mapbox/dark-v11/static/pin-l+c4c4c4(-77.0187,38.90227)/-77.0187,38.90227,14/320x150?access_token=YOUR_MAPBOX_ACCESS_TOKEN
We can further customize static map markers with any Maki icon. Here’s our new map marker using the restaurant Maki icon:
https://api.mapbox.com/styles/v1/mapbox/dark-v11/static/pin-l-restaurant+c4c4c4(-77.0187,38.90227)/-77.0187,38.90227,14/320x150?access_token=YOUR_MAPBOX_ACCESS_TOKEN
Update line 123 in the Code panel of the template in Twilio SendGrid to use the following updated image tag. Replace YOUR_MAPBOX_ACCESS_TOKEN
with your Mapbox access token.
<img
alt="{{merchant_data.name}} at {{merchant_data.address}} in {{merchant_data.city}}, {{merchant_data.state}}"
src="https://api.mapbox.com/styles/v1/mapbox/dark-v11/static/pin-l-restaurant+c4c4c4({{merchant_data.coordinates.longitude}},{{merchant_data.coordinates.latitude}})/{{merchant_data.coordinates.longitude}},{{merchant_data.coordinates.latitude}},14/320x150?access_token=YOUR_MAPBOX_ACCESS_TOKEN"
/>
The Maki icon set has over 200 icons covering most POI categories. If your use case is missing from the Maki icon set, you can use a custom icon for your map marker. The Mapbox Static Images API also supports multiple markers and paths, useful for transportation or rideshare email receipts.
Attribution
Our receipt map is small (320x150px
), and every pixel matters. The attribution in the bottom right-hand corner takes up a lot of space. We must provide attribution though since we’re using a Mapbox-designed template style (Mapbox Dark) with a Mapbox tileset (Mapbox Streets). To improve our design, we can move the attribution from the image to the body of the email itself (text attribution).
Add attribution=false
to the Static Images API URL on line 123 in the Code panel in Twilio SendGrid to remove attribution from the static map. Replace YOUR_MAPBOX_ACCESS_TOKEN
with your Mapbox access token.
<img
alt="{{merchant_data.name}} at {{merchant_data.address}} in {{merchant_data.city}}, {{merchant_data.state}}"
src="https://api.mapbox.com/styles/v1/mapbox/dark-v11/static/pin-l-restaurant+c4c4c4({{merchant_data.coordinates.longitude}},{{merchant_data.coordinates.latitude}})/{{merchant_data.coordinates.longitude}},{{merchant_data.coordinates.latitude}},14/320x150?attribution=false&access_token=YOUR_MAPBOX_ACCESS_TOKEN"
/>
Add the following table rows to the receipt table on line 178 in the Code panel to add text attribution:
{{#if merchant_data.coordinates }}
<tr>
<td
colspan="2"
style="
border-top: 1px dashed #e0e1e2;
line-height: 0px;
font-size: 0;
border-collapse: collapse;
padding: 0;
"
height="1"
>
</td>
</tr>
<tr>
<td align="center" style="font-size: smaller; color: #85898c">
Map data
<a style="color: #85898c" href="https://www.openstreetmap.org/copyright"
>(c) OpenStreetMap</a
>
contributors<br />
<a style="color: #85898c" href="https://www.mapbox.com/about/maps/"
>(c) Mapbox</a
>
<a style="color: #85898c" href="https://www.mapbox.com/contribute"
>Improve this map</a
>
</td>
</tr>
{{/if}}
Like our receipt static map, we wrapped both table rows in a Handlebars if conditional block in case the back-end JSON data is missing coordinates. We only need to show text attribution if we display a Mapbox map.
Your email template should look like this:
High-density display support
We have one final optimization before our receipt map is ready for production. Our map looks fuzzy and pixelated on high-density displays, like on an iPhone with Retina support.
Low-density static map
The Mapbox Static Images API supports high-density displays. Add @2x
after the parameters for image width and height:
https://api.mapbox.com/styles/v1/mapbox/dark-v11/static/pin-l-restaurant+c4c4c4(-77.0187,38.90227)/-77.0187,38.90227,14/320x150@2x?attribution=false&access_token=YOUR_MAPBOX_ACCESS_TOKEN
High-density static map
Enabling high-density display support will automatically double the image size on low-density displays. Our receipt map is 320x150@2x
or 640x300
pixels. Adding @2x
to our API call doubles the width of our receipt map on low-density displays.
Since our email receipt uses a fixed width of 320 pixels, we can set the image width to 320px
in our image HTML tag. For low-density displays, the map image will look the same as before at 320x150px
. High-density displays, like Retina users, will see a high-resolution map at 320x150px
.
Update line 123 in the Code panel of the template in Twilio SendGrid to use the following updated image tag. Replace YOUR_MAPBOX_ACCESS_TOKEN
with your Mapbox access token.
<img
alt="{{merchant_data.name}} at {{merchant_data.address}} in {{merchant_data.city}}, {{merchant_data.state}}"
src="https://api.mapbox.com/styles/v1/mapbox/dark-v11/static/pin-l-restaurant+c4c4c4({{merchant_data.coordinates.longitude}},{{merchant_data.coordinates.latitude}})/{{merchant_data.coordinates.longitude}},{{merchant_data.coordinates.latitude}},14/320x150@2x?attribution=false&access_token=YOUR_MAPBOX_ACCESS_TOKEN"
width="320px"
/>
One downside of our new static map is that low-density display users will download higher resolution images than needed, decreasing email performance. This approach is outside the scope of this tutorial, but we could use CSS media queries to dynamically determine high-density displays and add or remove @2x
from the Static Images API URL as needed.
Final product
You built an email receipt for a restaurant transaction using the Mapbox Static Images API. We used the Mapbox Dark map style and Maki icons to optimize our static map for email receipt use cases. Our receipt map supports high-density displays. We also used text attribution to improve the design of our email.
By the end of this tutorial, the HTML in the Code panel of the template in Twilio SendGrid should look like this. Replace YOUR_MAPBOX_ACCESS_TOKEN
with your Mapbox access token.
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Your receipt</title>
<meta
name="viewport"
content="initial-scale=1,maximum-scale=1,user-scalable=no"
/>
</head>
<body style="background-color: lightgray" bgcolor="lightgray">
<!-- Main body table -->
<table
cellpadding="0"
cellspacing="0"
border="0"
width="100%"
style="font-family: sans-serif"
>
<tr>
<td height="10px"> </td>
</tr>
<tr>
<td align="center">
<!-- Receipt table -->
<table
border="0"
cellpadding="5"
cellspacing="0"
style="width: 320px; background-color: #f2f4f5"
>
<tr>
<td
style="width: 100%; background-color: #444; text-align: center"
>
<img
alt="{{merchant_data.logo_alt}}"
width="157px"
height="77px"
src="{{merchant_data.logo}}"
/>
</td>
</tr>
<tr>
<td
align="center"
style="color: #3d454d; font-size: xx-large; font-weight: bolder"
>
{{amount}}
</td>
</tr>
<!-- Receipt items -->
<tr>
<td>
<table>
<tr>
<td
colspan="2"
style="
border-top: 1px dashed #e0e1e2;
line-height: 0px;
font-size: 0;
border-collapse: collapse;
padding: 0;
"
height="1"
>
</td>
</tr>
<tr>
<td colspan="2" height="5px"></td>
</tr>
{{#each items}}
<tr>
<td width="100%">{{this.description}}</td>
<td align="right">{{this.price}}</td>
</tr>
{{/each}}
</table>
</td>
</tr>
<!-- Total table -->
<tr>
<td>
<table>
<tr>
<td
colspan="2"
style="
border-top: 1px dashed #e0e1e2;
line-height: 0px;
font-size: 0;
border-collapse: collapse;
padding: 0;
"
height="1"
>
</td>
</tr>
<tr>
<td colspan="2" height="5px"></td>
</tr>
<tr>
<td width="100%">Total</td>
<td align="right" style="font-weight: bold">{{amount}}</td>
</tr>
</table>
</td>
</tr>
<!-- Map -->
{{#if merchant_data.coordinates }}
<tr>
<td style="padding: 0">
<img
alt="{{merchant_data.name}} at {{merchant_data.address}} in {{merchant_data.city}}, {{merchant_data.state}}"
src="https://api.mapbox.com/styles/v1/mapbox/dark-v10/static/pin-l-restaurant+c4c4c4({{merchant_data.coordinates.longitude}},{{merchant_data.coordinates.latitude}})/{{merchant_data.coordinates.longitude}},{{merchant_data.coordinates.latitude}},14/320x150@2x?attribution=false&access_token=YOUR_MAPBOX_ACCESS_TOKEN"
width="320px"
/>
</td>
</tr>
{{/if}}
<!-- Address -->
<tr>
<td align="center">
<span style="font-weight: bold">{{merchant_data.name}}</span
><br />
{{merchant_data.address}}<br />
{{merchant_data.city}} {{merchant_data.state}}
{{merchant_data.postal_code}}
</td>
</tr>
<!-- Transaction table -->
<tr>
<td align="center">
<table style="font-size: smaller; color: #85898c; width: 100%">
<tr>
<td
colspan="2"
style="
border-top: 1px dashed #e0e1e2;
line-height: 0px;
font-size: 0;
border-collapse: collapse;
padding: 0;
"
height="1"
>
</td>
</tr>
<tr>
<td colspan="2" height="5px"></td>
</tr>
<tr>
<td>Input type</td>
<td align="right">{{card_data.input_type}}</td>
</tr>
<tr>
<td>{{card_data.brand}} {{card_data.type}}</td>
<td align="right">xxxxxxxx{{card_data.last_four}}</td>
</tr>
<tr>
<td>Time</td>
<td align="right">
{{formatDate created "MM/DD/YYYY, h:mm A" timezoneOffset}}
</td>
</tr>
</table>
</td>
</tr>
<!-- Map attribution -->
{{#if merchant_data.coordinates }}
<tr>
<td
colspan="2"
style="
border-top: 1px dashed #e0e1e2;
line-height: 0px;
font-size: 0;
border-collapse: collapse;
padding: 0;
"
height="1"
>
</td>
</tr>
<tr>
<td align="center" style="font-size: smaller; color: #85898c">
Map data
<a
style="color: #85898c"
href="https://www.openstreetmap.org/copyright"
>(c) OpenStreetMap</a
>
contributors<br />
<a
style="color: #85898c"
href="https://www.mapbox.com/about/maps/"
>(c) Mapbox</a
>
<a
style="color: #85898c"
href="https://www.mapbox.com/contribute"
>Improve this map</a
>
</td>
</tr>
{{/if}}
</table>
</td>
</tr>
<tr>
<!-- Workaround for https://github.com/sendgrid/sendgrid-nodejs/issues/1231#issuecomment-771694665 -->
<td height="10px"><span style="display: none">ř</span></td>
</tr>
</table>
</body>
</html>
Next steps
Learn how to customize your receipt map further and build for finance or retail use cases.
- Learn how to geocode merchant addresses for your receipt map in part 2 of this tutorial series.
- Practice creating Static Images API requests in the Static Images 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.