I recently developed an open source subscription widget built using SendGrid’s Node.js API library. You can find more information about the motivations and capabilities of the project on the repository’s README page. While this is not an officially supported SendGrid library, the goal was to make an easily deployable, flexible widget any SendGrid customer can incorporate into an HTML page that collects existing and potential customers’ email addresses and other useful information to store within their Marketing Campaigns Contacts. After customers enter information into the form, they receive an email with a link to confirm their email address, and upon clicking the link, the recipient will be added to the given SendGrid customer’s contact list.

Beyond that basic functionality, the widget can also:

  • Add a user to a specific list segment, if specified
  • Include custom fields in the form, the inputs of which will be stored with a given contact
  • Send the confirmation email with an existing email template, if specified

This was an interesting project because it utilized a range of SendGrid’s capabilities and API endpoints, including:

This post will discuss the process of creating the widget and some of the thought processes that went into its implementation and design.

Widget Design Overview

The subscription widget functions by allowing SendGrid customers to deploy an app to Heroku using Heroku’s deploy button. To create the widget using the deploy button, SendGrid customers will need a Heroku account (you can create a free account here). However, the widget can theoretically be deployed to any hosting provider. The SendGrid customer can then change the endpoint to which the custom form makes its POST request to the URL of the newly deployed Heroku app. When a user submits the form, the request will then be processed by the app hosted on Heroku.

The app itself is a basic Node/Express application with two routes handling the signup process. The confirmEmail route sends users an email with a link to confirm their email address and uses a custom transactional template (if one is specified). The signup route adds a user to the SendGrid customer’s contact list and, if specified, also adds the user to a custom list segment. The signup route also handles any custom fields a SendGrid customer chooses to include in their custom form.

ConfirmEmail Route

The confirmEmail route is simply a post request to the v3/mail/send POST endpoint using SendGrid’s Node.js helper library. On a successful response, the user will be redirected to a page asking them to check their inbox for the confirmation email. On an unsuccessful response, the user will be redirected to a page asking them to re-enter their email address. For example, this may happen if a user were to enter an invalid email address.

 

The prepareEmail function returns a JSON object that will serve as the body of the API request.

 

The basic object creation process is fairly simple. In it, the recipient email address is inserted from the form submission. However, there are a few interesting things happening in the object creation process.

Basic Custom Arguments

Two custom arguments are included within personalizations: 1) type, which is set to ‘opt-in’, and 2) time_sent, which is set to the current time. These custom arguments are passed in the email header and will be used in determining whether a user should be added to a list in the confirmation process.

Template ID

After the initial object creation, we check if the SendGrid customer chose to utilize a custom template in the settings file and add it to the object if that is the case (null is the default value).  A custom template will take priority over mail text included in the body, so if the template ID is left as a null value, the message will default to the provided mail text.

Insert Link Substitution

We include a substitution for the term insert_link.  This will only be relevant if the SendGrid customer chooses to use a transactional template.  If that is the case, the insert_link term will be replaced by the actual link that is used to confirm a user’s email and redirect them to the appropriate success page.

Sending the Form Inputs as Custom Arguments

Finally, we add the values that the end user submitted to the email body as custom arguments. The end user’s submission is passed into the initial confirmEmail route as the request body, which we then pass into the prepareEmail function as a parameter. The request body contains an object with a set of key, value pairs representing the name of the input and the value the user submitted. We then loop through the object keys adding a custom argument to the email for each key, value pair. These values will be added to the end user’s contact information in the contact creation process.

Signup Route

The signup route is triggered by an event webhook that makes a POST request each time a user clicks the link provided in the confirmation email they receive. This route needs to take care of a few items in the contact creation process. We must do the following:

  • Check if the form contained any custom fields
  • Check if the custom fields exist in the SendGrid customer’s account and create them if the fields do not exist
  • Ensure this is an opt-in email as specified by the type in the email creation process
  • Ensure the link was clicked within 24 hours
  • Create the contact in the SendGrid customer’s account
  • Add the new contact to a specific list segment if one has been provided

Handling Custom Fields

The signup route calls the function addUserToList. This function is called within the route so that we can send the status after the process is complete inside of a callback. The first thing we do inside of this function is create an object containing all custom fields the form contains and an array containing the custom fields that are not provided by default for all contacts (email, first_name, last_name).

A POST request triggered by event webhooks contains all of the email-related information, including the email’s headers, subject, text, etc. All we care about are the custom arguments that have been provided, which are contained as an object within the first element of the request body. However, the object also contains a variety of fields we don’t need for the process of handling custom fields, which we place in an array called ignoreFields.

 

We then loop through the custom arguments to create the object and array with the custom fields mentioned earlier. We will pass the full custom fields object into the body in the contact creation process, but not until we add custom fields, as necessary, in a prior step.

 

We then call the function checkAndAddCustomFields with two parameters, the custom field array and a callback, which is where we’ll take care of creating the contact.  It is important to first check and add any custom fields because the endpoint will throw an error if you try to create a contact with a custom field that does not exist.

The checkAndAddCustomFields function first makes a GET request to the /v3/contactdb/custom_fields endpoint to retrieve the contact database’s existing fields. It then compares the list of existing custom fields with the list of submitted fields that were passed as a parameter, and if there are any submitted fields that are not included in the list of existing custom fields, those new fields are added to the fieldsToCreate array. If there aren’t any fields to create, the callback function is called. However, if there are any fields to create, we make a POST request to the /v3/contactdb/custom_fields endpoint for each new custom field that will be created.

Creating the New Contact

Once the custom fields have been created, we create a new contact by making a POST request to the /v3/contactdb/recipients endpoint, passing in the custom fields as the body to the request. We then check if the SendGrid customer chose to add users to a given list segment and add them to the given segment, if that is the case.  The response to the contact creation API request includes the contact ID(s) of newly created contact(s) as an array called persisted_recipients. Using the contact ID provided in the response and list ID provided by the SendGrid customer, we then make a POST request to the /v3/contactdb/lists/{listId}/recipients/{contactID} endpoint.



Devin Chasanoff
Devin is a Developer Evangelist at SendGrid. As part of the Community Development team, Devin helps run the SendGrid Accelerate program. Devin loves hacking, hustling and helping start-ups change the world.