Kickbox integration
What Kickbox is
Kickbox is an email verification service. This means you can send them email addresses and they give you some information back, including a risk rating for bounces. This can be extremely useful especially if you have not sent the contact emails for a while. Many sources estimate a data decay of over 30% a year, and we really should keep the bounce rates <1%. So a solution would be to ask a service like Kickbox, neverbounce or zerobounce if they believe the email will bounce and act accourdingly.
Purpose of this workflow
While the standard Kickbox integration can only verify the entire database, we want to do this on a case-by-case basis. The benefit of that is that we have near-realtime information when it counts and will not pay for the verification of email addresses that we do not consider worth that verification at the moment.
The custom code we will write can be implemented in a workflow that may check if the email address has not been verified in the last X days and if there is no confirmed open in the last X days.
Prework
For this workflow we have set up the following secrets:
- 'PrivateAppKickBox', which is a private app key that has the correct permissions. More info on how to set that up can be found in the HubSpot Developer Docs.
- 'KICKBOXAPI'. This is the API key that we use to authenticate our request with Kickbox. At the time of writing they give the first 100 verifications free so you can test if everything works.
And we have also created the following custom property:
- kickbox_sendex_score - Number
Kickbox does have more interesting fields available, but for simplicity we will only create these.
We highly recommend of also creating a property called 'kickbox_last_update' or something similar, and in a non-coded workflow step include the date when the coded workflow last ran. This may help you prevent the workflows from running too often.
Writing the basics
The code below will be the starting point. Let's go over that: We are going to start with importing libraries that will be useful for this workflow. The list of the available standard libraries is available in HubSpot's developer documentation, but if needed one can also load an external library.
In our quest to connect with HubSpot's APIs and write back the received data from Kickbox, we're going to employ a crucial companion: the '@hubspot/api-client' library. If you're new to the programming world, you might wonder, what exactly is a library? In the realm of JavaScript, libraries are reusable sets of code that provide functionality to ease the development process. They're like a toolbox, filled with tools that we can use to build our program without reinventing the wheel for common tasks.
So, in the case of '@hubspot/api-client', it provides an interface to interact with HubSpot's APIs, streamlining the process of making requests and handling responses. Simply put, it enables our program to communicate with HubSpot's APIs in a more straightforward and efficient way.
Next, we've chosen to implement the 'axios' library for making HTTP requests to Kickbox. Axios is a modern library for making requests to other API endpoints that allows promises. This means that it can use placeholders for the 'answer' and still continue with the code. It also provides automatic transformation from the more basic JSON data to directly usable code, making it an excellent choice for our modern application.
The exports.main is the function that is called when the workflow is executed. Inside this function we will have our code. Please do note the 'event' and 'callback.
'event' is the object enrolled in the workflow and we will call that below.
'callback' can be used to make a value available in the workflow outside the custom code. More info on that later.
Inside the 'export.main' function we do two things: we connect to HubSpot thought the library called earlier. For this two things need to be in place:
- We need to create a private app for this workflow. If you do not know how to do that: please refer to the developer documentation.
- We need to store that as a secret. In this case we called the secret 'PrivateAppKickBox' that is set as a process variable.
And on the last line before closing we use the value as put in the inputfield and make that available in the rest of the code.
// Import required libraries
const hubspot = require('@hubspot/api-client');
const axios = require('axios');
exports.main = (event, callback) => {
// Create a new HubSpot Client
const hubspotClient = new hubspot.Client({
accessToken: process.env.PrivateAppKickBox
});
// Get the email address of the currently enrolled contact
const email = event.inputFields['email'];
} // end exports.main
Error handeling
We do now have all the basic components and can start actually doing something. But how can we be sure we are doing everything correct?
In order to handle errors gracefully, we will not tell the code to run, but instead try to see if it can make it run, and catch errors if they occur and also can decide on how to deal with them.
For this part we left the 'try' bit empty and will focus on the catch error. We will write that to the console and also throw an error. If the workflow throws an error like this, HubSpot will try again. Besides flaws in the code, errors may be caused for instance by rate limits. Having the system retry helps there.
// Import required libraries
const hubspot = require('@hubspot/api-client');
const axios = require('axios');
exports.main = async (event, callback) => {
// Create a new HubSpot Client
const hubspotClient = new hubspot.Client({
accessToken: process.env.PrivateAppKickBox
});
// Get the email address of the currently enrolled contact
const email = event.inputFields['email'];
try {
} catch (err) {
console.error(err);
// We will automatically retry when the code fails because of a rate limiting error from the HubSpot API.
throw err;
}
}
Connecting to Kickbox
The initial snippet merely imports libraries and sets up the email variable. Now, let's put them to use by connecting to the Kickbox API. Fortunately Kickbox has super-clear documentation that mentions the API endpoint we need to hit:
GET https://api.kickbox.com/v2/verify?email=
We will focus on the URL first and amend this so it takes it uses the email variable and the API secret.
Now that we have that in place we are going to make the call using axios. We The kickbox docs showed us that it is a get request, which makes sense.
If there is a valid response, we are going to store that response and do a console log to see which values come back. This is obviously also in the documentation, but it provides us a nice checkpoint to see if we made any errors.
Here within the results we also introduce the callback. Using this we can make an output available. More on using that later.
If we do not get a valid response, we are going to call an error for the kickbox_result. Now let's run the code below and see what is in our 'data' array.
// Verify the email using Kickbox API:
const response = await axios.get(`https://api.kickbox.com/v2/verify?email=${email}&apikey=${process.env.KickboxKey}`);
const data = response.data; // we could deconstruct this in the line above by changing response to { data }
console.log(data);
What we have so far
This is what we get back in the console. Looks good and now we know which values we are going to use. Please note that we are not using all fields in or writeback as we only created the properties in the prerequesits that we believe will be most useful.
result: risky
reason: low_deliverability
role: false
free: false
disposable: false
accept_all: true
did_you_mean: null
sendex: 0.75
email: ******@hubspot.com
user: ******
domain: hubspot.com
success: true
message: null
Storing the Sendex Score in HubSpot and Returning the Value
In our pursuit to interact with HubSpot's CRM, we're utilizing the @hubspot/api-client library. By connecting with the pre-configured hubspotClient
, we aim to update a contact's property with valuable data.
If you refer to the HubSpot API documentation for contacts, it becomes evident that, in SDK v9, updating contact properties should typically be done using hubspotClient.crm.contacts.basicApi.patch
. However, there's a nuance here. For workflows in the current version, v8, this method isn't functional. Upon inspecting available methods using a console log (which has been commented out in the code), it's clear that 'update' is the appropriate method to use for our purpose.
In the code, we're focused on a singular property: the Sendex score from the Kickbox API response. This score is stored in HubSpot's CRM under the property name 'kickbox_sendex_score'.
Additionally, to facilitate potential subsequent actions in the workflow, the code also sends back the Sendex score. This score, encapsulated in the 'outputFields', can be used to make decisions regarding contact interactions or categorizations in other parts of the workflow.
await hubspotClient.crm.contacts.basicApi.update(event.object.objectId, {
"properties": {
"kickbox_sendex_score": data.sendex
}
});
// Send response back with the sendex score:
callback({
outputFields: {
sendex_score: data.sendex
}
});
Wrapping up the code
We now have the completed code as below. and while this may look like a massive codeblock to beginners: do keep in mind that 13 out of the 52 lines are blank or comment-only. Some lines like line 12 could have easily been integrated in the code directly. But for this example we prefer clarity and readability.
// Import libraries
const hubspot = require('@hubspot/api-client');
const axios = require('axios');
exports.main = async (event, callback) => {
// Connect to HubSpot API:
const hubspotClient = new hubspot.Client({ accessToken: process.env.academy_secret });
// Retrieve the email from inputFields:
const email = event.inputFields['email'];
try {
// Verify the email using Kickbox API:
const response = await axios.get(`https://api.kickbox.com/v2/verify?email=${email}&apikey=${process.env.KickboxKey}`);
const data = response.data; // we could deconstruct this in the line above by changing response to { data }
// Update the contact properties in HubSpot. Based on the documentation for SDK v9 this should be done with hubspotClient.crm.contacts.basicApi.patch.
// Refer: https://developers.hubspot.com/docs/api/crm/contacts
// but in the current v8 for workflows that is not working.
// Checking what is available with the request below. This gave us 'update'
// console.log(Object.getOwnPropertyNames(Object.getPrototypeOf(hubspotClient.crm.contacts.basicApi)));
await hubspotClient.crm.contacts.basicApi.update(event.object.objectId, {
"properties": {
"kickbox_sendex_score": data.sendex
}
});
// Send response back with the sendex score:
callback({
outputFields: {
sendex_score: data.sendex
}
});
} catch (err) {
console.error(err);
// We will automatically retry when the code fails because of a rate limiting error from the HubSpot API.
throw err;
}
}
Making the output available
While we did make the output available in the code, we still need to tell the workflow action to make this available in the entire workflow. We do this by calling it as a Data output:
Please note that there always are standard outputs, while we can also create custom outputs like we do here.
Hope this help you on your way with opening the endless possiblities of custom coded workflows.
Want to follow along? That is exactly what Kyle's workshop lets you!
At Halloween 2023 we recorded creating just this exact workflow from scratch so you can easily follow along!