Skip to main content

Add a static map to an email receipt, part 1: front-end template

Prerequisite

Familiarity with front-end development concepts.

Tutorial series: Receipt map
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.
arrow-downDownload starter code

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

Email delivery systems
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:

  1. Download the starter code zip file from the Getting Started section.
  2. Unzip the zip file.
  3. Open template.html and transaction.json from the zip file in a text editor.
  4. Login to Twilio SendGrid in a web browser.
  5. Click on Design Library in the left-hand menu.
  6. Click the Create Email Design button.
  7. Select the Code Editor option.
  8. Click on Test Data.
  9. Copy and paste transaction.json into the Test Data panel.
  10. Click on Code.
  11. Delete the pre-populated HTML code in the Code panel.
  12. Copy and paste template.html into the Code panel.

Your email template should look like the image below:

circlecirclecircle
arrow-leftarrow-rightrotate
heartmapboxmenu

To verify our template works, we can send a test email directly from Twilio SendGrid:

  1. Click on Settings in the top left-hand corner.
  2. Type Receipt for Email Design Name.
  3. Type Your receipt for Subject.
  4. Click on Test your email.
  5. Type the email address for your verified sender identity in From Address.
  6. Enter the email address to receive the test email in Email Addresses under Send a Test Email.
  7. Click on the Send Test Message button.

Your test email should look like this:

circlecirclecircle
arrow-leftarrow-rightrotate
heartmapboxmenu

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 and 180) of the center point for the static map. For our restaurant, this is -77.0187.
  • Latitude: The latitude (a number between -85.0511 and 85.0511) of the center point for the static map. For our restaurant, this is 38.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 is 1280x1280.
  • 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= <UserAccessToken /> 

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 levelschevron-right

Update 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= <UserAccessToken /> 

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= <UserAccessToken /> 

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= <UserAccessToken /> 

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&amp;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"
>
&nbsp;
</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= <UserAccessToken /> 

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.

circlecirclecircle
arrow-leftarrow-rightrotate
heartmapboxmenu

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">&nbsp;</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"
>
&nbsp;
</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"
>
&nbsp;
</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&amp;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"
>
&nbsp;
</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"
>
&nbsp;
</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.

Was this page helpful?