Introduction
With SmartMail, you can trigger automations based on various events which occur in your system.
What are events?
Events simply describe something that happened on your website or business. Events always have a type, a time and a subject.
Some examples of events are:
- A customer made an order on your website on July 4, 2017, 10:00am.
- A visitor visited a page on your website on February 26, 2016, 8:53pm.
- A product was restocked to a level of 100 units on June 5, 2016, 9:41am.
- An order was shipped to a customer on Oct 12, 2015, 9:34am
- A subscriber signed up for a newsletter on…
- A recipient clicked on a link in a campaign on…
You get the picture ;)
How are events used in SmartMail?
Events are used within SmartMail to:
- Trigger automations, either immediately or with a delay
- Segment customers based on actions that they’ve done (or haven’t done)
Sending Events
Event Format
All events must have a type. SmartMail has some built-in event types for e-commerce which you should use if you are sending e-commerce related events. In addition, you can send us any other type of event, but we will not be able to perform advanced e-commerce related analytics on them. The built-in event types are specified in the Built-In Events section.
Making the Request
<?php
$storeId = "AAABBB"; // Unique per SmartMail account
$apiKey = "SuperSecretApiKey"; // Get this from SmartMail
$eventType = "customers/update"; // See "Event types"
$domain = "kingslanding.com"; // Your store's domain
$platform = "MAGENTO"; // Per-platform setting
$headers = [
"x-domain: $domain",
"x-platform: $platform",
"x-token: $apiKey",
"x-event-type: $eventType",
'Content-Type: application/json; charset=UTF-8'
];
$customerData = [
'created_at' => '2017-04-04T15:12:21-05:00',
'email' => 'john.snow@thewall.org',
'first_name' => 'John',
'last_name' => 'Snow',
'updated_at' => '2017-04-04T15:12:21-05:00',
'accepts_marketing' => true
];
$dataString = json_encode($customerData);
$ch = curl_init("https://webhooks.smartmail.io/webhooks/?storeId=$storeId");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, $dataString);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$result = curl_exec($ch);
var storeId = "AAABBB"; // Unique per SmartMail account - get yours from the API setting page
var eventType = "customers/update"; // See "Event types"
var data = JSON.stringify({
"email": "john.snow@thewall.org",
"first_name": "John",
"last_name": "Snow",
"accepts_marketing": true
});
// If run client-side, use our tracking script:
var _rmData = _rmData || [];
_rmData.push(['setStoreKey', storeId]); // Only needs to be run once on the page
_rmData.push(['track', eventType, data]);
// End client-side code
// If run server-side, send the request directly to our webhook endpoint, and include your API token
var token ="<Your API token>"; // Never expose this client-side!
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.addEventListener("readystatechange", function () {
if (this.readyState === 4) {
console.log(this.responseText);
}
});
xhr.open("POST", "https://webhooks.smartmail.io/webhooks/store/"+storeId);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.setRequestHeader("x-event-type", eventType);
xhr.setRequestHeader("x-token", token);
xhr.setRequestHeader("Cache-Control", "no-cache");
xhr.send(data);
The request must be a POST
request to our endpoint.
The request needs to include the following headers:
Header | Value | Example |
---|---|---|
Content-Type | application/json; charset=UTF-8 | |
x-event-type | The event type | customers/update |
x-token | Your API Key (not necessary on client-side events) | abc123 |
x-domain | Your domain (optional) | my.store.com |
x-platform | Your e-commerce platform (optional) | MAGENTO1 |
The body
of the request is simply the event JSON itself.
E-Commerce Event Types
Following is a list of e-commerce Event Types and their JSON format. Each event type has several mandatory fields, which must by included in the JSON. The rest are optional, but the more data you send the more accurate SmartMail can be in analyzing the data and communicating with your customer.
The event type must be specified in the x-event-type
header.
customers/create
{
"accepts_marketing": true,
"birthdate": "1997-02-26",
"created_at": "2012-02-15T15:12:21-05:00",
"default_address": {
"country": "United States",
"country_code": "USA",
"province_code": "MO",
"zip": "45415-3423",
"phone": "1-923-555-5555"
},
"email": "guy@smartmail.io",
"first_name": "Guy",
"gender": "M",
"groups": [
{
"id": 1,
"name": "VIP Customers"
}
],
"id": 1234,
"info": {},
"last_name": "Harel",
"rewards": {
"points": 10
},
"tags": [
"tag1",
"tag2"
],
"title": "Mr.",
"updated_at": "2012-02-15T15:12:21-05:00",
"verified_email": true
}
Used to inform SmartMail about customers being created or updated.
Mandatory Fields
Field | Data type | Description |
---|---|---|
email or id |
Valid email and/or user id (string) | Send at least one of these fields. If the email of a user has changed, the user_id will be used to locate the record. |
accepts_marketing |
Boolean | If the user did not explicitly opt in or out on the website, send null here. |
created_at |
customers/update
Same format as customers/create. Used to inform SmartMail about a customer update.
If the customer has changed her email address, make sure to send a valid id
. SmartMail will then update the
customer record based on this id. Otherwise, a new customer might be created with the new email.
orders/create
{
"created_at": "2012-02-15T15:12:21-05:00",
"currency": "USD",
"customer": {
"accepts_marketing": true,
"birthdate": "1997-02-26",
"created_at": "2012-02-15T15:12:21-05:00",
"default_address": {
"country": "United States",
"country_code": "USA",
"province_code": "MO",
"zip": "45415-3423",
"phone": "1-923-555-5555"
},
"email": "guy@smartmail.io",
"first_name": "Guy",
"gender": "M",
"groups": [
{
"id": 1,
"name": "VIP Customers"
}
],
"id": 1234,
"info": {},
"last_name": "Harel",
"updated_at": "2012-02-15T15:12:21-05:00",
"tags": "tag1,tag2",
"title": "Mr.",
"verified_email": true
},
"discount_codes": [
{
"code": "10OFF",
"amount": 20,
"type": "fixed_amount"
}
],
"email": "guy@smartmail.io",
"fulfillment_status": "string",
"id": 1234,
"line_items": [
{
"product_id": 1234,
"quantity": 1,
"sku": "SKU1234",
"name": "Blue Shirt, Size S",
"url": "https://my.cool.store/catalog/cool-gadget.html",
"image": "https://my.cool.store/images/cool-gadget.jpg",
"title": "Shirt",
"variant_title": "Blue",
"vendor": "Castro",
"price": 100,
"taxable": true,
"tax_lines": [
{
"title": "VAT",
"price": 18,
"rate": 0.18
}
],
"added_at": "2012-02-15T15:12:21-05:00",
}
],
"note": "Please don't break this!",
"shipping_lines": [
{
"title": "Fedex 2-day",
"price": 20,
"code": "fedex-2-day"
}
],
"status": {
"code": "S",
"name": "Shipped"
},
"financial_status": {
"code": "S",
"name": "Shipped"
},
"subtotal_price": 100,
"tax_lines": [
{
"title": "VAT",
"price": 18,
"rate": 0.18
}
],
"taxes_included": true,
"test": true,
"total_discounts": 20,
"total_line_items_price": 100,
"total_price": 100,
"total_shipping": 20,
"total_tax": 18,
"total_weight": 150,
"updated_at": "2012-02-15T15:12:21-05:00"
}
Used to inform SmartMail about a new order in the system. Send us much information as possible here.
Note that the order
information is a nested object which also includes information about the customer
and lineitems
.
Mandatory fields
Field | Data type | Description |
---|---|---|
id |
String | The order id in your backoffice |
created_at |
Datetime string (with timezone) | Order creation date. |
customer.accepts_marketing |
Boolean | Is the customer opted in to marketing emails |
customer.created_at |
Datetime string (with timezone) | The time this customer was created |
customer.updated_at |
Datetime string (with timezone) | The time this customer was last updated |
customer.email |
Valid email | The email associated with the customer. |
lineitems[].product_id |
String | Your backoffice’s product id |
lineitems[].quantity |
Numeric | The amount of this item in the order |
lineitems[].sku |
String | The SKU of this item |
lineitems[].name |
String | The name of the product to be displayed to the user |
lineitems[].url |
URL | A link to this item’s product page in your store |
lineitems[].image |
URL | URL of the image of this item or product |
lineitems[].price |
Numeric | The price of this item in this specific order |
status.name |
String | The name of the order status |
subtotal_price |
Numeric | Order subtotal |
total_discounts |
Numeric | The total of all discounts applied. This should be a positive number. |
total_shipping |
Numeric | Shipping costs for this order |
total_tax |
Numeric | Tax added to this order |
total_price |
Numeric | The total that the customer was charged for this order |
orders/update
Same format as orders/create. Inform SmartMail about an order being updated (status changed, products changed, etc)
products/create
{
"body_html": "This is an <strong>awesome</strong> product!",
"categories": [
{
"code": 12,
"name": "Men's clothing"
}
],
"created_at": "2012-02-15T15:12:21-05:00",
"id": 1234,
"image": {
"id": 1111,
"product_id": 1234,
"created_at": "2012-02-15T15:12:21-05:00",
"updated_at": "2012-02-15T15:12:21-05:00",
"src": "https://my.cool.store/image1.jpg",
"variant_ids": [
null
]
},
"images": [
{
"id": 1111,
"product_id": 1234,
"created_at": "2012-02-15T15:12:21-05:00",
"updated_at": "2012-02-15T15:12:21-05:00",
"src": "https://my.cool.store/image1.jpg",
"variant_ids": [
null
]
}
],
"options": [
{
"id": 4444,
"name": "Color",
"values": [
"red"
]
}
],
"published_at": "2012-02-15T15:12:21-05:00",
"product_exists": true,
"sku": "SKU1234",
"tags": "cool,new",
"title": "Cool Gadget",
"updated_at": "2012-02-15T15:12:21-05:00",
"url": "https://my.cool.store/catalog/cool-gadget.html",
"variants": [
{
"barcode": "string",
"currency": "USD",
"created_at": "2012-02-15T15:12:21-05:00",
"fulfillment_service": "string",
"id": 12345,
"image": "https://my.cool.store/images/image1.jpg",
"inventory_quantity": 12,
"price": 199.23,
"product_id": 1234,
"sku": "SKU1234",
"taxable": true,
"title": "Blue Shirt",
"option1": "Blue",
"updated_at": "2012-02-15T15:12:21-05:00",
"requires_shipping": true,
"weight": 150,
"weight_unit": "g"
}
],
"vendor": "Samsung"
}
Inform SmartMail about a new product.
What are product variants?
A variant is a “subtype” of a more generic product. For example, for the product “Nike Air Jordan”, you would have different variants such as White / Size 12 or Black / Size 12.
For an iPhone 7, you might have the Silver / 32GB and the Gold / 256GB variants.
If you don’t have such variants (for example: you sell A Chair), then the product would have exactly one variant, with most of the properties (SKU, id, etc) being duplicated into the variant.
Mandatory fields
Field | Data type | Description |
---|---|---|
id |
String | The product id in your backoffice |
sku |
String | The product SKU |
created_at |
Datetime string (with timezone) | The time this product was created |
image.src |
URL | URL of the main product image thumbnail |
product_exists |
Boolean | Is this product currently for sale in the store? |
title |
String | Short title describing the product |
url |
URL | Link to the product page on the store website |
vendor |
String | Name of the product vendor/manufacturer |
variants[].id |
String | The backoffice ID of this specific variant |
variants[].image |
URL | The image URL of this variant |
variants[].inventory_quantity |
Numeric | # of items youhave in stock |
variants[].product_id |
Numeric | The id of this variant’s parent |
variants[].sku |
String | The SKU of this product variant |
variants[].title |
String | The title of this specific variant |
products/update
Similar to products/create, inform SmartMail about a product that’s been updated.
products/delete
Products cannot be deleted, but they can be updated so that they are no longer available for recommendations.
To do this, update the product with product_exists=false
.
carts/create
{
"abandoned_checkout_url": "https://my.cool.store/carts/2341234sdfasdf",
"billing_address": {
"country": "United States",
"country_code": "USA",
"province_code": "MO",
"zip": "45415-3423",
"phone": "1-923-555-5555"
},
"accepts_marketing": true,
"cart_token": "cart_abc",
"created_at": "2012-02-15T15:12:21-05:00",
"currency": "USD",
"customer": {
"accepts_marketing": true,
"birthdate": "1997-02-26",
"created_at": "2012-02-15T15:12:21-05:00",
"default_address": {
"country": "United States",
"country_code": "USA",
"province_code": "MO",
"zip": "45415-3423",
"phone": "1-923-555-5555"
},
"email": "guy@smartmail.io",
"first_name": "Guy",
"gender": "M",
"groups": [
{
"id": 1,
"name": "VIP Customers"
}
],
"id": 1234,
"info": {},
"last_name": "Harel",
"updated_at": "2012-02-15T15:12:21-05:00",
"tags": "tag1,tag2",
"title": "Mr.",
"verified_email": true
},
"discount_codes": [
{
"code": "10OFF",
"amount": 20,
"type": "fixed_amount"
}
],
"email": "guy@smartmail.io",
"fulfillment_status": "string",
"id": 1234,
"line_items": [
{
"product_id": 1234,
"quantity": 1,
"sku": "SKU1234",
"name": "Blue Shirt, Size S",
"title": "Shirt",
"variant_title": "Blue",
"vendor": "Castro",
"price": 100,
"taxable": true,
"tax_lines": [
{
"title": "VAT",
"price": 18,
"rate": 0.18
}
],
"added_at": "2012-02-15T15:12:21-05:00"
}
],
"note": "Please don't break this!",
"shipping_address": {
"country": "United States",
"country_code": "USA",
"province_code": "MO",
"zip": "45415-3423",
"phone": "1-923-555-5555"
},
"shipping_lines": [
{
"title": "Fedex 2-day",
"price": 20,
"code": "fedex-2-day"
}
],
"subtotal_price": 100,
"tax_lines": [
{
"title": "VAT",
"price": 18,
"rate": 0.18
}
],
"taxes_included": true,
"total_discounts": 20,
"total_line_items_price": 100,
"total_price": 100,
"total_shipping": 20,
"total_tax": 18,
"total_weight": 150,
"updated_at": "2012-02-15T15:12:21-05:00"
}
When a user browses the site, they will start (hopefully) adding products to the cart. As long as the cart did not turn
into an actual order, you should use the carts
endpoints in order to let SmartMail know about them. If the cart is
anonymous (most are…), the customer
and emails
fields can be sent as null
.
Mandatory fields
Field | Data type | Description |
---|---|---|
abandoned_checkout_url |
URL | This URL should lead to the cart contents. Optimally, this URL should work on any browser. |
accepts_marketing |
Boolean | Does this customer accept email marketing? |
created_at |
Datetime string (with timezone) | Time that this cart was originally created |
currency |
String | 3-letter code of this cart’s currency |
customer |
Boolean | If this cart is not a guest (anonymouns) cart, send the customer information here. Otherwise set to null |
customer.accepts_marketing |
Boolean | Does this customer accept marketing? |
customer.email |
The email address of this customer | |
customer.id |
String | The backoffice customer id |
email |
String | For guest checkouts, this can be set even if there is no registered customer. If unknown, set as null |
id |
String | The cart ID in the backoffice. Used when updating (and deleting) the cart. |
title |
String | Short title describing the product |
line_items |
Array | The products in the cart. Same as in the order |
total_discounts |
Numeric | Discounts given for this cart |
total_price |
Numeric | Total price of this cart, incl tax, discounts and shipping |
updated_at |
Datetime (with timezone) | Last time this cart was updated. Very important for timing the emails. |
carts/update
Same format as carts/create. Make sure to keep the same id
so that carts don’t get duplicated.
carts/delete
{
"id": 1234
}
Be sure to send a carts/delete
event with the cart ID once a cart turns into an order.
newsletter/subscribe
{
"email": "shopper@gmail.com",
"first_name": "Edgar",
"last_name": "Poe",
"tags": ["politics", "sport"]
}
var storeId = "AAABBB"; // Unique per SmartMail account - get yours from the API setting page
var eventType = "newsletter/subscribe"; // See "Event types"
var data = JSON.stringify({
"email": "shopper@gmail.com",
"first_name": "Edgar",
"last_name": "Poe",
"tags": ["politics", "sport"]
});
// If run client-side, use our tracking script:
var _rmData = _rmData || [];
_rmData.push(['setStoreKey', storeId]); // Only needs to be run once on the page
_rmData.push(['track', eventType, data]);
// End client-side code
Send this event to inform SmartMail of a customer’s explicit wish to receive email newsletters.
When SmartMail received this event, we will not send an opt-in confirmation. If you would like SmartMail to handle
the double opt-in process, send a customer/create
event instead, with accepts_marketing: true
.
newsletter/unsubscribe
{
"email": "shopper@gmail.com"
}
var storeId = "AAABBB"; // Unique per SmartMail account - get yours from the API setting page
var eventType = "newsletter/unsubscribe"; // See "Event types"
var data = JSON.stringify({
"email": "shopper@gmail.com"
});
// If run client-side, use our tracking script:
var _rmData = _rmData || [];
_rmData.push(['setStoreKey', storeId]); // Only needs to be run once on the page
_rmData.push(['track', eventType, data]);
// End client-side code
Inform SmartMail that a shopper wishes to unsubscribe. We will add this email to our suppression list, so that if the
customer record is updated from different sources, we will not send marketing materials to this user, even if the
customer record contains accepts_marketing: true
.
If you would like to resubscribe the customer after an unsusbcribe event, send an explicit newsletter/subscribe
event.
Receiving Events
In many cases, in addition to sending events to SmartMail, you’d also like to be informed on actions that SmartMail performs or knows about, such as email sends, opens, clicks and unsubscribes.
SmartMail supports publishing these events via JSON webhooks. You supply an SSL (https) endpoint, and we will POST events to that endpoint as detailed below.
Setup
Setup your receiving endpoint in your SmartMail Settings page.
Webhook Structure
POST /endpoint.yoursite.com HTTP/1.1
X-Event-Hmac-SHA256: 4293d6f6232a9d346fbd5da0ba0c9a86851675507b750509373f227414f3cb98
X-Event-Topic: newsletter/subscribed
Content-Type: application/json
User-Agent: SmartMail
Cache-Control: no-cache
{
"email": "john@doe.com",
"ip_signup": "10.0.0.1",
"ip_opt": "10.0.0.1",
"properties": {
"first_name": "John",
"last_name": "Doe",
"campaign": "Lead gen #3"
}
}
Our POST to your endpoint will have the structure shown on the right:
Headers
Header | Purpose |
---|---|
X-Event-Hmac-SHA256 |
Message digest for authentication - see below |
X-Event-Topic |
The event topic, or type |
Body
The request body will contain JSON-formatted text as detailed in the topics below.
Authentication
<?php
define('SHARED_SECRET', '<shared secret>');
function verify_webhook($data, $hmac_header)
{
$calculated_hmac = base64_encode(hash_hmac('sha256', $data, SHARED_SECRET, true));
return hash_equals($hmac_header, $calculated_hmac);
}
$hmac_header = $_SERVER['HTTP_X_EVENT_HMAC_SHA256'];
$data = file_get_contents('php://input');
$verified = verify_webhook($data, $hmac_header);
if ($verified) {
$payload = json_decode($data);
$eventTopic = $_SERVER['HTTP_X_EVENT_TOPIC'];
} else {
error_log("Invalid signature");
}
In order to verify that the request came from SmartMail, you can use the X-Event-Hmac-SHA256 header, which includes a digital signature of the message contents (body only - not including the headers). We create the signature using the Shared Secret provided in the app. On the right is a PHP example on how to verify the signature.
Retries and Errors
We expect your endpoint to respond within 5 seconds with a 2xx return code. In case the request exceeds the timeout or returns with a 5xx error code, we will continue retrying in increasing intervals, for 2 hours. If we fail after 2 hours, we will stop retrying and will send an email to the account administrator.
Event Topics and Payloads
Common fields:
Field | Purpose |
---|---|
timestamp | The timestamp of the event itself |
The recipient’s email address (cc or bcc will not trigger events) | |
campaign_name | The plain-text name of the campaign that triggered this email |
campaign_group | The plain-text name of the campaign group |
campaign_id | The internal SmartMail id of the campaign |
umk | A unique identifier per email message |
useragent | The useragent header as sent by the recipient’s email client |
ip | The ip address of the recipient |
email/sent
{
"timestamp": "2018-04-12T12:50:00+00:00",
"email": "john@doe.com",
"campaign_name": "Abandoned cart #1",
"campaign_id": 13118,
"umk": "5acf31d0b22410.937575625acf31d0b"
}
Sent when an email is sent to the recipient.
email/clicked
{
"timestamp": "2018-04-12T12:50:00+00:00",
"email": "john@doe.com",
"campaign_name": "Abandoned cart #1",
"campaign_id": 13118,
"umk": "5acf31d0b22410.937575625acf31d0b",
"link_url": "https://my.website.com/some-page",
"useragent": "Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko Firefox/11.0 (via ggpht.com GoogleImageProxy)",
"ip": "1.2.3.4"
}
Sent when a recipient clicks on a link inside an email
email/opened
{
"timestamp": "2018-04-12T12:50:00+00:00",
"email": "john@doe.com",
"campaign_name": "Abandoned cart #1",
"campaign_id": 13118,
"umk": "5acf31d0b22410.937575625acf31d0b",
"useragent": "Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko Firefox/11.0 (via ggpht.com GoogleImageProxy)",
"ip": "1.2.3.4"
}
Sent when an email is opened. This is not 100% correct because open tracking is done via images, which are sometimes blocked and sometimes opened by the receiving mail server and not the actual, human recipient.
email/delivered
{
"timestamp": "2018-04-12T12:50:00+00:00",
"email": "john@doe.com",
"campaign_name": "Abandoned cart #1",
"campaign_id": 13118,
"umk": "5acf31d0b22410.937575625acf31d0b"
}
Sent when the recipient’s email server acknowledges receipt of the message. This does not necessarily mean that the email reached the inbox.
email/bounced
{
"timestamp": "2018-04-12T12:50:00+00:00",
"email": "john@doe.com",
"campaign_name": "Abandoned cart #1",
"campaign_id": 13118,
"umk": "5acf31d0b22410.937575625acf31d0b",
"reason": "554 5.4.14 Hop count exceeded - possible mail loop ATTR34 [SN1NAM04FT063.eop-NAM04.prod.protection.outlook.com]"
}
Sent when the receiving email server does not accept the message. May signal a soft bounce (for example: mailbox full) or a hard bounce (address doesn’t exist).
email/spam
{
"timestamp": "2018-04-12T12:50:00+00:00",
"email": "john@doe.com",
"campaign_name": "Abandoned cart #1",
"campaign_id": 13118,
"umk": "5acf31d0b22410.937575625acf31d0b"
}
Sent when an email is marked as spam. Not all email servers send back a spam notification, gmail in particular does not.
email/unsubscribed
{
"timestamp": "2018-04-12T12:50:00+00:00",
"email": "john@doe.com",
"campaign_name": "Abandoned cart #1",
"campaign_id": 13118,
"umk": "5acf31d0b22410.937575625acf31d0b"
}
newsletter/subscribed
{
"timestamp": "2018-04-12T12:50:00+00:00",
"optin_timestamp": "2018-04-12T12:55:00+00:00",
"email": "john@doe.com",
"signup_ip" : "1.2.3.4",
"optin_ip": "1.2.3.4"
}
Sent when a customer’s marketing preferences changes to “yes”, either by signing up to a popup, by an API call, or by an
admin in the app. signup_ip
(optional) is the IP address used to initially sign up the user (might a server IP if this was an API call).
optin_ip
(optional) is the IP used to click the “confirm email” link in the double opt-in email, if sent.
Errors
The API may return the following error codes:
Error Code | Meaning |
---|---|
400 | Bad Request – The request is malformed or is missing mandatory fields |
401 | Unauthorized – Wrong API key |
403 | Forbidden |
404 | Not Found |
406 | Not Acceptable – You requested a format that isn’t json |
418 | I’m a teapot |
429 | Too Many Requests – You are making too many requests in a limited time |
500 | Internal Server Error – We had a problem with our server. Try again later. |
503 | Service Unavailable – We’re temporarily offline for maintenance. Please try again later. |