NAV Navbar

SmartMail
php javascript

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:

You get the picture ;)

How are events used in SmartMail?

Events are used within SmartMail to:

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 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
email 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.