# Sources The data you use to personalize a message may be personal in nature (first name, birth date, city) or based on user behavior (purchase history, pageviews, app opens). # Personalizing messages using Attributes > Attributes are metadata used for audience segmentation and personalization. You can use Attributes in a message body to personalize content for each user. For example, if you were sending a birthday message to your audience, you could target the audience using an [Attribute](https://www.airship.com/docs/reference/glossary/#attributes) for a specific date and also use relevant Attributes within the body of the message to personalize based on an individual's specific age, location, etc. Use [Handlebars](https://www.airship.com/docs/reference/glossary/#handlebars) to reference Attributes in message content. The image below shows a push notification with title `It's your birthday, {{name}}!` and text `We heard you're turning {{age}}! Here's a gift to help you celebrate.` Using [preview data](https://www.airship.com/docs/guides/personalization/previewing/), you can populate the message preview to see how it will appear to your users. ![Personalizing a message using age and name Attributes](https://www.airship.com/docs/images/personalize-attributes-birthday_hu_468bdfc4256c8d5.webp) *Personalizing a message using age and name Attributes* Since Attributes can represent different kinds of values, make sure you know the format of your Attributes before using them in a message or template. See [Attribute types](https://www.airship.com/docs/guides/audience/attributes/about/#attribute-types) in *About Attributes*. ### Personalizing content with JSON Attributes JSON Attributes are provided as an object with keys corresponding to instance IDs each with a value of the associated instance. To use JSON Attributes in personalization, you can either reference a specific instance by ID or loop through the instances using the `#each` operator. See the [Looping through objects and arrays](https://www.airship.com/docs/guides/personalization/handlebars/loop/) guide. > **Warning:** Do not include an instance ID in conditionals or other queries. Instance IDs are only for use with the APIs for [setting and removing JSON Attributes](https://www.airship.com/docs/guides/audience/attributes/setting/#setting-and-removing-json-attributes). Use the form `attribute_name.PropertyName` only. In the dashboard device preview, other forms, such as `attribute_name#instance_id.PropertyName`, may render correctly, but sends will fail. > > For example, for an Attribute `acme_ticketing_window_json` with a `Cities` property, use `acme_ticketing_window_json.Cities` inside conditionals: > > ```text > {{#if ($contains acme_ticketing_window_json.Cities "Guadalajara")}} > Guadalajara: February 25 at 11:00 (ET) / 17:00 (CET) > {{else}}{{/if}} > {{#if ($contains acme_ticketing_window_json.Cities "Philadelphia")}} > Philadelphia: February 25 at 11:00 (ET) / 17:00 (CET) > {{else}}{{/if}} > ``` Let's look at an example. This is a JSON schema for an Attribute with ID `players`: ```text { name: string cool_factor: number is_cool: boolean inventory: [ { name: string age: number enchanted: boolean custom_name: string magic_properties: [ { id: string bonus: number } ] } ] } ``` An example of a JSON Attribute using the above schema and having two saved instances might look like this: ```text { "player1": { "name":"Ganthur", "cool_factor":7, "is_cool":true, "inventory":[ { "name":"Broadsword", "age":177, "enchanted":true, "custom_name":"Matilda", "magic_properties":[ { "id":"flaming", "bonus":20 } ] } ] }, "player2": { "name":"Balfor", "cool_factor":1, "is_cool":false, "inventory":[ { "name":"Mighty axe", "age":23, "enchanted":false, "custom_name":"Brunhild", "magic_properties":[ { "id":"indestructible", "bonus":4 } ] } ] } } ``` You can use the `this` expression to refer to the current item in the loop and `@index` for the zero-index number of the item. In this example we will create a numbered list of player names and increment the `@index` operator by one using the `add` helper so the list starts with `1`: ```text {{#each players}} {{add @index 1}} - {{this.name}} {{/each}} ``` You can use the `as |name|` syntax to make your loop more readable. Let's add each player's list of inventory names to our example: ```text {{#each players as |player|}} {{add @index 1}} - {{player.name}}. Inventory: {{#each player.inventory as |item|}} {{item.name}} {{/each}} {{/each}} ``` Add an `if` conditional statement to display a message only for players with a sword in their inventory that has the `flaming` magic property and display its custom name: ```text {{#each players as |player|}} {{#each player.inventory as |item|}} {{#if (eq item.name "sword")}} {{#each item.magic_properties as |property|}} {{#if (eq property.id "flaming")}} Congrats on your flaming sword "{{item.custom_name}}" {{/if}} {{/each}} {{/if}} {{/each}} {{/each}} ``` You can also refer to a specific instance of the JSON Attribute using its instance ID. For example, `{{players.player1}}`. # Personalize your Create and Send messages > Create and Send supports personalization using Handlebars. When using [Create and Send](https://www.airship.com/docs/reference/glossary/#create_and_send), you can personalize values directly in the `create_and_send` array, or you can include in your CSV data additional properties that you want to use to personalize messages — any column name or property that is not prefaced with `ua_`. Use `{{column_name}}` or `{{property_name}}` in the body of the message. All values in CSV uploads, including numbers, are represented as strings and cannot be used with [math helpers](https://www.airship.com/docs/guides/personalization/handlebars/math-helpers/). If you want to personalize your message using both Attributes and information from a CSV, make sure that the properties in the CSV are unique from your Attributes. If an Attribute has the same name as a property in the CSV, Airship will try to use the value in the CSV. See [Object and Array Notation for Create and Send](https://www.airship.com/docs/guides/personalization/handlebars/basics/#object-and-array-notation-for-create-and-send) for using complex arrays and objects. ## Enabling personalization When using bulk audiences (`bulk_id`) with personalization, include `"personalization": true` in the `options` object of your request. Personalization is enabled automatically when using the direct `create_and_send` array approach. **Create and Send request with personalization:** ```http POST /api/create-and-send HTTP/1.1 Authorization: Basic Accept: application/vnd.urbanairship+json; version=3 Content-Type: application/json { "audience": { "create_and_send": [ { "ua_msisdn": "15551234567", "ua_sender": "12345", "ua_opted_in": "2021-04-29T10:34:22", "name": "New Customer, Esq.", "from_city": "City", "from_state": "OR" } ] }, "device_types": [ "SMS" ], "message_type": "commercial", "notification": { "sms": { "template" : { "fields" : { "alert": "Hi {{name}}! I hear you're from {{from_city}}, {{from_state}}!", } } } } } ``` # Personalize messages using Custom Events > Personalize message content with values from the Custom Events used to trigger Automations, Scenes, and Sequences. Custom Events help you track user activities and conversions from your app, website, etc. When you trigger an [Automation](https://www.airship.com/docs/reference/glossary/#automation), [Scene](https://www.airship.com/docs/reference/glossary/#scene), or [Sequence](https://www.airship.com/docs/reference/glossary/#sequence) with a [Custom Event](https://www.airship.com/docs/reference/glossary/#custom_event), you can use properties from the event to personalize the resulting messages. For Sequences, you can reuse Custom Event properties in any message in the Sequence, which can help remind your audience about items they left in their shopping cart, or that an event that they're interested in is happening soon. Automations and Sequences support server-side and client-side Custom Events for personalization. Scenes support client-side Custom Events only. > **Tip:** You can also personalize messages using [Attributes](https://www.airship.com/docs/reference/glossary/#attributes), even when you don't use Custom Event triggers. ## Referencing properties in message content Custom Events are associated with a channel or a [Named User](https://www.airship.com/docs/reference/glossary/#named_user) and carry properties that you can use to trigger and personalize your messages. Start by creating an Automation, Scene, or Sequence using the Custom Event trigger, and select one or more Custom Events or event properties. When writing the message, you can reference properties from the Custom Event trigger in your message or template. For example, you could iterate over the items a user purchased by referencing an `items_purchased` property. ```text {{#each items_purchased}} {{qty}} x {{item_name}} = {{item_total}} {{/each}} {{total}} ``` If you reference a property that is not in an event, is null, or empty, the reference will be empty in your message, and your message may not make sense. Use the [default handler](https://www.airship.com/docs/guides/personalization/handlebars/basics/#def) `$def` to set default values, so that your message still makes sense if variables are empty or absent. For example, `{{$def name "you"}}` would insert `you` in the message if the `name` property in your Custom Event is empty or missing. ## Custom Event personalization example When personalizing messages or templates for a Custom Event-triggered Automation, Scene, or Sequence, you can use anything in the Custom Event's `properties` object. The following is an example Custom Event. We will use that to send a personalized receipt in a Message Center message. **Custom Event:** ```json { "occurred": "{{event_time}}", "user": { "named_user_id": "user" }, "body": { "name": "purchase", "subscribe": true, "properties": { "customer_name": "Dave", "total": 48, "cost_units": "USD", "purchase": [ { "qty": 4, "item": "MLB regulation baseball", "per": "$12", "total": "$48" }, { "qty": 2, "item": "MLB official hat", "per": "$15", "total": "$30" } ] } } } ``` Because Custom Event properties can include arrays, objects, and arrays of objects, we can [loop through](https://www.airship.com/docs/guides/personalization/handlebars/loop/) complex Custom Event properties using `{{#each}}`. Next we put it all together with the default handler to craft the message: ```handlebars Hi, {{$def customer_name "valued customer"}}! Thanks for your purchase of: {{#each purchase}} {{qty}} x {{per}} {{item}} = {{this.total}} {{/each}} Total: ${{total}} {{cost_units}} Your order is being processed. We'll message you again when it ships! ``` The result will look like this: ```handlebars Hi, Dave! Thanks for your purchase of: 4 x $12 MLB regulation baseball = $48 2 x $15 MLB official hat = $30 Total: $78 USD Your order is being processed. We'll message you again when it ships! ``` # External Data Feeds > {{< glossary_definition "external_feed" >}} ## About External Data Feeds Using External Data Feeds can help you tell your audience about things they might be interested in, without having to send custom events to Airship or assign [Attributes](https://www.airship.com/docs/reference/glossary/#attributes). For example, if your audience requests weather updates, you can set up an External Data Feed to fetch weather data when you send your message and personalize messages with relevant weather updates for each member of your audience. The following is the general workflow for using External Data Feeds in messages: 1. [Create an External Data Feed](#create-a-feed) in the dashboard. 1. [Reference a feed when entering content in a message or template](#using-a-feed-in-messages). You'll create a merge field representing a value you want to personalize, surrounded by double curly braces `{{ }}`, also known as [Handlebars](https://www.airship.com/docs/reference/glossary/#handlebars). Airship replaces the merge field (and braces) with the data specified by the merge field at send time, just like mail merge in a word processor or mail client. Example feed variables in a message: ```text {{#feed "feed_id"}} Content referencing the feed, like {{first_name}} or {{mtn_weather}}. {{/feed}} ``` 1. [Set failure behavior and variable values when sending the message](#setting-feed-failure-behavior-and-variable-values). ### Throttling and caching Personalization using External Data Feeds can mean a lot of API calls in a short period of time. With throttling, you can set a frequency rate for your requests. The minimum rate is 100 requests per second. You can also cache responses from your feeds and use them for multiple recipients. You may want to cache a response if you don't expect your feed to return a different response when using the same feed URL and parameters. You can enable throttling or caching for a feed but not both. ### Object locations You can enter names and paths for objects in your feed so they are available in the code copying helper in the [Interactive Editor](https://www.airship.com/docs/reference/glossary/#interactive_editor). See [Code helper](#code-helper). Use JSON dot notation to enter the paths to the variables you want to reference. For example, if you have a user object, you might want to access the first name of your user. You would enter `firstName` as the *Name* for the data and `user.firstName` as the *Object location*. See also [Object and array notation](https://www.airship.com/docs/guides/personalization/handlebars/basics/#dot-notation). ### External API requirements Your external API must operate in the following ways to work with External Data Feeds: * Return the correct `Content-Type` header for the response. We support `application/json`. * Return the correct `Content-Encoding` header if you are compressing your responses. We support gzip for compression. * Return your content as a JSON object. We do not support top-level JSON arrays. Array content must be nested in an object. ## Create a feed You can create up to 10 feeds per project. [Contact Support](https://support.airship.com/) to request increasing this limit. 1. Next to your project name, select the dropdown menu (▼), then **Settings**. 1. Under **Project settings**, select **External Data Feeds**. 1. Select **Create external data feed**. 1. Configure the feed settings, and then select **Next** to move on: | Setting | Description | | --- | --- | | **Friendly name** | This is the feed name as it appears in the Airship dashboard. | | **Feed ID** | This is how you will reference the feed. The ID must be unique, without spaces or special characters. Use a feed ID that is meaningful to use and relatively easy to type. **You cannot change the feed ID later.** | | **Description** | This is a summary of the data returned by the feed. | | **Request URL** and **Default values** | This is the URL to retrieve data from, also called the *feed URL*. The URL is your domain, protocol, path, and any variables you want to use to personalize your messages. For example, `https://api.example.com/products/`. The default value fields appear automatically based on the request URL. Enter a default value for each send time variable used in the request URL. See [Message-level and user-level variables](#message-level-and-user-level-variables). | | **Confirm access rights for request URL** | Check the box to confirm. | | **Headers** | Optional. Specify any HTTP request headers that may be required to successfully communicate with the external feed. For example, `authorization` headers.

Header values support Handlebars as well, so you can populate header values from attributes and other personalization sources. Header information is not a part of the request URL. Select **Add header**, then enter a *Key* and *Value*. Repeat for additional headers. | 1. (Optional) Configure [throttling or caching](#throttling-and-caching), and then select **Next** to move on: | Setting | Description | | --- | --- | | **Throttle requests** | Enable to set a frequency rate for your requests. Enter the number of API requests per second. The minimum rate is 100 requests per second. | | **Cache response** | Check box to enable. | 1. (Optional) Configure [object locations](#object-locations). Select **Add object location** and enter a name and object location. 1. Select **Save**. ### Message-level and user-level variables The [request URL](#create-a-feed) supports two types of variables. You can use both in the same URL: | Variable type | Syntax | Resolved from | Scope | |---|---|---|---| | **Message-level (send time)** | `[[var]]` | Default value set in the feed, overridden per message | Same value for all recipients | | **User-level** | `{{var}}` (standard [Handlebars](https://www.airship.com/docs/reference/glossary/#handlebars)) | Audience data such as [Attributes](https://www.airship.com/docs/reference/glossary/#attributes) and custom event properties | Different value per recipient | Use **message-level variables** to reuse a single feed for different purposes by changing the variable value at send time. You set a default value when creating the feed and can override it when [sending the message](#setting-feed-failure-behavior-and-variable-values). Use **user-level variables** to personalize the feed request per recipient. Airship resolves each variable from the recipient's data, so the feed request URL can differ for each member of your audience. This example uses two message-level variables (`[[program]]` and `[[contentID]]`) to fetch different content per campaign without creating separate feeds: `https://api.example.com/[[program]]/[[contentID]]` This example combines a message-level variable (`[[msg_purpose]]`) with user-level variables (`{{first_name}}` and `{{last_name}}`), so each recipient gets a personalized request for the same campaign: `https://api.example.com/[[msg_purpose]]?user={{first_name}}%20{{last_name}}` If your users have [Named User](https://www.airship.com/docs/reference/glossary/#named_user) IDs set, you can use the `ua_named_user_id` [default attribute](https://www.airship.com/docs/reference/data-collection/attributes/#default-attributes) to fetch feed data per user: `https://api.example.com/[[msg_purpose]]?user={{ua_named_user_id}}` ## Using a feed in messages You add your data feed to messages in two parts: 1. Formatting the message content 1. Entering default values for the variables you referenced in your message and determining how to handle the message if the feed fails ### Formatting content Formatting your message content is identical in the dashboard and the API. In the dashboard you add content when configuring a template or in the Content step in a composer. **Content format for External Data Feeds** ```text {{#feed "feed_id"}} Content referencing the feed, like {{first_name}} or {{mtn_weather}}. {{/feed}} ``` If your feed URL includes send time variables, you can override their default values in the `#feed` block as `{{#feed "feed_id" var="value"}}`. Or you can [set a value in the *Delivery* step](#setting-feed-failure-behavior-and-variable-values) that applies to all of the channels selected for the message. ### Code helper When using the [Interactive Editor](https://www.airship.com/docs/reference/glossary/#interactive_editor) to create content for a template, email, landing page, or Message Center message, we provide a helper for inserting feed code. Available code: * Object locations — Paths defined in the feed settings. * `#feed` blocks — Available for all feeds in your project. A preformatted block that includes the feed ID: ```text {{#feed "feed_id"}} {{/feed}} ``` When adding content: 1. Select *Data Feed* in the left sidebar. 1. Select ▼ for a feed. 1. Hover over a feed name or object location and select the duplicate icon ( ) to copy the code. 1. Select a field in your message content and paste the copied data. ![Copied and pasted feed code in an App template](https://www.airship.com/docs/images/feed-in-template_hu_47c7a83c3fd90937.webp) *Copied and pasted feed code in an App template* #### Differentiating data sources You can use reference variables (or [Merge Fields](https://www.airship.com/docs/reference/glossary/#merge_field)) that aren't a part of a feed and variables that are part of a feed in the same `#feed` block. For instance, if your content uses project attribute `first_name` and the feed also has a property `first_name`, if you don't namespace it, the feed data will take precedence over the project attribute. To differentiate the data sources, set the feed namespace using `as |alias|`. This ensures that Airship uses the proper data for the variables in your messages. In the example below, we can differentiate between attributes (`first_name` and `user_id`) and feed properties based on the parent namespace set within the `feed` and `each` blocks. ```text {{#feed "featured_products" region="us" as |result|}} Hi, {{first_name}}. Take a look at {{#each (limit result.products 2) as |product|}} {{product.name}}: {{product.price}} https://example.com/us/{{user_id}}/featured/{{urlEncode product.slug}}/ {{else}} Products that might interest you at https://example.com/us/products/featured {{/each}} {{else}} Couldn't fetch featured products! {{/feed}} ``` ### Setting feed failure behavior and variable values In the *Delivery* step of a composer, you must configure each feed listed in *External data feed options*. * **Failure behavior** — Determine how your message is handled if the feed fails. If your message still makes sense without a feed, you may want to send the message even if your data feed fails. Also consider how ongoing (automated and recurring) messages will be affected if you later [delete the feed](#managing-external-data-feeds). Select *Abort sending the message* or *Send message without this data*. See also: [Success, Retry, and Error Conditions](#success-retry-and-error-conditions). * **Send time variables** — If your feed URL contains send time variables, each one will be listed as `Default value for [var]` with its default value. You can change the value here to override the default. Changes apply to the current message only. ![External data feed settings in a composer's Delivery step](https://www.airship.com/docs/images/feed-options_hu_e93b42f76f4de163.webp) *External data feed settings in a composer's Delivery step* With the API, you set the failure (error) behavior in the [External Data Feed References Object](https://www.airship.com/docs/developer/rest-api/ua/schemas/external-data-feeds-references/). When using a data feed you must include a [`templates` object](https://www.airship.com/docs/developer/rest-api/ua/operations/personalization/) in the payload or set the `personalization` option to `true`. * The value for `name` is the Feed ID you set when [creating your feed](#create-a-feed). * The possible values for `on_error` are `cancel` and `continue`. These are equivalent to the dashboard options *Abort sending the message* and *Send message without this data*. `continue` will result in sending the message despite an error. Otherwise, a feed failure will prevent Airship from sending your message. **Example push request using an External Data Feed** ```json { "feed_references": { "feeds": [ { "name": "featured_products", "params": { "region": "us" }, "on_error": "continue" } ] } } ``` ## Managing External Data Feeds To see a list of all your feeds: 1. Next to your project name, select the dropdown menu (▼), then **Settings**. 1. Under **Project settings**, select **External Data Feeds**. To edit all fields except the Feed ID, select the more menu icon (⋯) for a feed, select **Edit**, make your changes, and then select **Save**. If you make changes to the Request URL, you will need to reconfirm the request URL domain. You must confirm access rights for request URL every time you edit a feed. To delete a feed, select the more menu icon (⋯) for a feed, and then select **Delete**. > **Warning:** * If you delete an External Data Feed, any messages content referencing its data will be handled according to the message's [feed failure behavior settings](#setting-feed-failure-behavior-and-variable-values). > * Your External Data Feed is deleted immediately. There is no confirmation and your External Data Feed cannot be restored once deleted. ## Success, Retry, and Error Conditions

Airship considers any 2xx response from a feed to be a success and will continue a message using the feed response. Airship considers any 3xx, 4xx, and 5xx codes (except 502/503) to be error conditions.

HTTP status Result Behavior
2xx Success Airship uses the feed response, even if empty.
3xx Error Error behavior determined by user.
4xx Error Error behavior determined by user.
5xx Error Error behavior determined by user.
timeout (60 sec) Retry Up to 4 retries, 60 sec between attempts.
502/503 Retry Up to 4 retries, 60 sec between attempts.
## Send Aborted event The [Real-Time Data Streaming](https://www.airship.com/docs/reference/glossary/#rtds) `SEND_ABORTED` event occurs when a push is dropped from our system before delivery is attempted. Device information for the device that did not receive the push is included with `SEND_ABORTED` events, and the `reason` property gives more information about the error. For additional information and a sample event, see the [Send Aborted event](https://www.airship.com/docs/developer/rest-api/connect/schemas/events/#send-aborted) in the *Real-Time Data Streaming API* reference. # Personalization using message namespace properties > Message namespace properties are dynamic values that are automatically populated based on the details of a messaging campaign. These properties are the message name, platforms, send date, [Push ID](https://www.airship.com/docs/reference/glossary/#push_id), and [Campaign Categories](https://www.airship.com/docs/reference/glossary/#campaign_categories). They serve a crucial role in personalizing messages and tailoring content by allowing you to dynamically adjust [URL Parameters](https://www.airship.com/docs/reference/glossary/#url_parameters) and implement conditional logic using [Handlebars](https://www.airship.com/docs/reference/glossary/#handlebars). Using these properties, you can create more relevant and targeted messaging experiences for your audience. You can also use [Text](https://www.airship.com/docs/guides/personalization/handlebars/text-helpers/) and [Date and time](https://www.airship.com/docs/guides/personalization/handlebars/date-time-helpers/) helpers to transform content in your Handlebars expressions. The properties are available for personalization for all channels and message types except for [In-App Automations](https://www.airship.com/docs/reference/glossary/#iaa) and [Scenes](https://www.airship.com/docs/reference/glossary/#scene). Message namespace properties: | Namespace property | Description | Format | Example value | | --- | --- | --- | --- | | {{$message.push_id}} | The Airship [Push ID](https://www.airship.com/docs/reference/glossary/#push_id) assigned to the message | String | 50b521d9-4cd1-4c08-99c1-cabcf0bf5a2b | | {{$message.platforms}} | The platforms enabled for this message: iOS, Android, Email, SMS, etc. | Comma separated text | ios,email | | {{$message.date}} | The date the message was sent, in UTC | [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) | 1983-01-31T12:36:35 | | {{$message.name}} | The message name set in a [Composer](https://www.airship.com/docs/reference/glossary/#composer) | String | Autumn promo | | {{$message.campaign_categories}} | The [Campaign Categories](https://www.airship.com/docs/reference/glossary/#campaign_categories) defined in the message | String, 64-character maximum per category | spring_newsletter 2024 | # Coupons > Add promotional codes to your messages. {{< badge "axp" >}} ## About Coupons ![A landing page with a promotional code — Barcode requires JavaScript](https://www.airship.com/docs/images/coupons-landing-page_hu_9f85950f7b0c85f1.webp) *A landing page with a promotional code — Barcode requires JavaScript* Like other forms of personalization in Airship, with Coupons you create a merge field representing a value you want to personalize, surrounded by double curly braces `{{ }}`, also known as [Handlebars](https://www.airship.com/docs/reference/glossary/#handlebars). Airship replaces the merge field (and braces) with the data specified by the merge field at send time — just like mail merge in a word processor or mail client. The source of this data (including your promotional codes) is a CSV file you upload to Airship and the campaign you define for the codes. We refer to this source as a *feed* for the campaign, and you'll see the term in the dashboard and in the required formatting for your message content. Example uses: * Provide exclusive discounts or rewards to retain or transition your customers and build loyalty * Clear out excess or slow-moving inventory * Re-engage existing customers with your business Coupons are supported for messages in both the dashboard and API. --- Promotional codes are rendered in plain text in your messages. You can use the JsBarcode library to [render the codes as barcodes instead](#rendering-as-a-barcode). Codes are limited to 100 requests per second and are best used with automation. Consider this rate if you intend to distribute codes via broadcasts to large audiences. ### Supported message types You can add codes to any message content you create using the Message, A/B Test, Automation, and Sequence composers: * Push notification * In-app message (standard format) * Message Center (Not supported for A/B Tests) * Landing page * Web push notification * Email * SMS * Open channel You can also add codes to [Templates](https://www.airship.com/docs/reference/glossary/#template) for: * Push notification * Message Center (can also be used for landing pages) * Web push notification * Email * SMS * Open channel The same message types and template content are supported in the API. Coupons are not supported for In-App Automations or Scenes, as these are ephemeral and not a good place to show the promotional code. We suggest using a link in these formats to other messaging formats that are persistent. ### Remaining code notifications When you upload your CSV file, a value equal to 10% of the number of codes is stored and checked when a code is used in a message. We'll notify you by email when 90% of codes have been used in messages and again at 100%. You can specify up to five email addresses for notification. Add `coupons@partner-alerts.airship.com` as a contact in your email client to ensure these messages are not marked as spam. We repeat the calculation when you [add more codes to a campaign](#managing-campaigns-and-adding-more-codes). ### Coupons workflow The following is the general workflow for using promotional codes in messages. For illustration, we are using retail loyalty as an example. 1. [Create your CSV file.](#formatting-your-csv-file) The first row must contain headers `coupon_code` and `campaign`. Each additional row must contain a unique alphanumeric code and the same ID for the campaign. (You can add more information in your file, but we're keeping it simple for this example). **Example CSV** ```text coupon_code,campaign RS8E3589,2023loyalty RS8E3590,2023loyalty RS8E3591,2023loyalty ``` 1. [Create a campaign](#creating-a-campaign) in the dashboard. You will upload your CSV file and define the following: * *Campaign name* — This is used for identification in the list of all campaigns in your project. * *Campaign ID* — This must match what you put in your CSV. You will use the ID when creating your messages so Airship can use codes from the right campaign. * *Validity period* — The effective and expiration dates and times for the campaign and its codes. In our examples the campaign ID is `2023loyalty`. 1. [Create your content](#adding-codes-to-messages) using the following format. In all content, use the feed ID `couponing`. ```text {{#feed "couponing"}} Content referencing the feed, like a {{coupon_code}}. {{/feed}} ``` Using our loyalty example, your message would look like this in the [Interactive Editor](https://www.airship.com/docs/reference/glossary/#interactive_editor): ![Coupons](https://www.airship.com/docs/images/interactive-coupon_hu_5ef052723c1b0ec3.webp) 1. [Specify the campaign ID and determine failure behavior](#specifying-the-campaign-id-and-feed-failure-behavior) when sending the message. * In the dashboard, you'll do this in the Delivery step of a composer. * With the API, you'll make these specifications in the External Data Feed References object. > **Important:** As a best practice, verify the campaign ID, its validity period, and that it has available codes prior to sending the message. The feed will fail if the expiration date-time has passed or does not have remaining codes. > > You can view the number of available codes for each campaign: > > 1\. Next to your project name, select the dropdown menu (▼), then **Settings**.
> 2\. Under **Project settings**, select **Coupons**. --- Boom! We just sent codes to our audience. Now what happens? This is what Airship does next: * Populates your message content at send time, using one code from your CSV file per message * Checks the number of remaining codes so we can process your [remaining code notifications](#remaining-code-notifications) * Generates an event every time a code is used in a message (See next section) > **Note:** Codes do not populate messages as ordered in your CSV file. For example, if your CSV file has rows with codes in order `1001, 1002, 1003`, they may be sent to users in order `1003, 1001, 1002`. ### Reporting Codes are associated with the [Channel ID](https://www.airship.com/docs/reference/glossary/#channel_id) of the message recipient at send time. This association is recorded as a [Custom Event](https://www.airship.com/docs/reference/glossary/#custom_event) with name `coupon_assigned`. The event properties use keys `coupon_code` and `campaign`. `coupon_code_alt` will also appear if included in your CSV file. The property values are the actual code used in the message and the campaign ID. The event is recorded the first time a code for a given campaign is assigned to a Channel ID. Additional events are not recorded if you send messages to the same Channel ID that references the same code. **Example Coupons custom event** ```json { "name": "coupon_assigned", "properties": { "coupon_code": "RS8E3589", "campaign": "2023loyalty" } } ``` ## Formatting your CSV file When uploading in the dashboard, maximum file size 1.5 GB. There is no file size limit when using SFTP [to add more codes](#managing-campaigns-and-adding-more-codes) to a campaign. To ensure the support of special characters and accents, the file must be encoded to UTF-8 without BOM. * `campaign` — Required. The campaign ID. Used to determine which campaign to use for populating message content. Ignored when uploaded in the dashboard. Must match an existing campaign when uploaded via SFTP. * `coupon_code` — Required. A unique alphanumeric value to render in a message. Duplicate values are ignored and not added to the campaign as available codes. * `coupon_code_alt` Optional. An alternative version of the coupon code (such as an online code) that can be rendered in a message. Must be a unique alphanumeric value. * `starts_time` — Optional. The effective date-time for the campaign. Must be in ISO 8601 date-time format `YYYY-MM-DDThh:mm:ss`. Defaults to current date-time if not provided. **Ignored when file is uploaded in the dashboard.** Updates the campaign effective date-time in the dashboard when uploaded via SFTP. * `ends_time` — Optional. The expiration date-time for the campaign. Must be in ISO 8601 date-time format `YYYY-MM-DDThh:mm:ss`. **Ignored when uploaded in the dashboard.** Updates or sets the campaign expiration date-time in the dashboard when uploaded via SFTP. ### Pre-assigning coupon codes By default, coupon codes are randomly assigned to recipients at send time. You can pre-assign coupon codes to specific contacts by adding user IDs to your CSV file. Pre-assigned coupon codes are not added to the campaign as available codes or included in the [remaining code notifications](#remaining-code-notifications). To pre-assign codes to [Channel IDs](https://www.airship.com/docs/reference/glossary/#channel_id), add these headers and values to your CSV: * `identifier_type` — Set to `channel_id`. * `identifier` — The Channel ID of the contact to pre-assign to the coupon code. To pre-assign codes using [Named User](https://www.airship.com/docs/reference/glossary/#named_user) IDs, you must first contact your technical account manager or [Airship Support](https://support.airship.com/) to enable the feature. Once enabled, use the same headers as above, but with these values: * `identifier_type` — Set to `named_user`. * `identifier` — The Named User ID of the contact to pre-assign to the coupon code. ### Sample CSV for Coupons Use the selector to see sample files containing all headers and only the required headers. #### Only required headers ```text coupon_code,campaign myCouponCode0,myCampaign myCouponCode1,myCampaign ``` #### All headers ```text coupon_code,coupon_code_alt,campaign,starts_time,ends_time,identifier,identifier_type myCouponCode0,myOnlineCode0,myCampaign,2023-11-20T21:43:44,2024-11-20T21:43:44 myCouponCode1,myOnlinceCode1,myCampaign,2023-11-20T21:43:44,2024-11-20T21:43:44,9c36e8c7-5a73-47c0-9716-99fd3d4197d5,channel_id ``` ## Creating a campaign 1. Next to your project name, select the dropdown menu (▼), then **Settings**. 1. Under **Project settings**, select **Coupons**. 1. Click **Create coupon campaign**. 1. Configure fields: * *Campaign name* — Used for identification in the list of all coupon campaigns in your project. * *Campaign ID* — Used to determine which campaign to use for populating message content. * Format supports alphanumeric characters, dashes, and underscores. * The ID must be unique for a project, without spaces or special characters. * The `campaign` value in your CSV file will be ignored. * **You cannot change the campaign ID later.** * *Effective/Expiration dates* — Optional. The beginning and end dates and times for the campaign and its codes. * If the effective date and time are not specified, they default to the date-time of saving the campaign. * **If you set a start date and time, you cannot edit them later**. * *Remaining codes contact* — The email address used to contact you with [remaining code notifications](#remaining-code-notifications). You can enter up to five comma-separated addresses. Addresses entered here will replace those entered for every campaign in the project. 1. Upload your CSV file. 1. Click **Save campaign**. You will return to the [list of all campaigns in the project](#managing-campaigns-and-adding-more-codes). ## Adding codes to messages You add codes to messages in two parts: 1. Formatting the message content 1. Specifying the campaign to use as the data feed and determining how to handle the message if the feed fails ### Formatting content Formatting your message content is identical in the dashboard and the API. In the dashboard you add content when configuring a template or in the Content step in a composer. #### Feed without campaign ```text {{#feed "couponing"}} Content referencing the feed, like a {{coupon_code}}. {{/feed}} ``` #### Feed with campaign ```text {{#feed "couponing" campaign="FallSavings"}} Content referencing the feed, like a {{coupon_code}}. {{/feed}} ``` The feed ID is always `couponing`. To render coupon codes, the campaign ID, or effective and expiration dates, use `{{coupon_code}}`, `{{campaign}}`, `{{campaign_start_date}}`, and `{{campaign_end_date}}`. Dates are rendered in ISO 8601 date-time format: `YYYY-MM-DDThh:mm:ss`. If you want dates to appear differently in your message, use [Date and time helpers](https://www.airship.com/docs/guides/personalization/handlebars/date-time-helpers/). Examples for a campaign with expiration date 2023-10-15: * To render the date as `10/15/2023`, you would enter it like so in your message content: `{{dateFormat campaign_end_date locale="en-US" format="SHORT"}}`. * To render the date as `Oct 15, 2023`, you would use: `{{dateFormat campaign_end_date locale="en-US" format="MEDIUM"}}`. You can use reference variables (or [Merge Fields](https://www.airship.com/docs/reference/glossary/#merge_field)) that aren't a part of a feed and variables that are part of a feed in the same `#feed` block. See [Differentiating data sources](https://www.airship.com/docs/guides/personalization/sources/external-data-feeds/#differentiating-data-sources) in *External Data Feeds*. > **Tip:** When using the [Interactive Editor](https://www.airship.com/docs/reference/glossary/#interactive_editor) to create content for a template, email, landing page, or Message Center message, we provide a helper for inserting the feed formatting. > > 1. Click *Data Feed* in the left sidebar. > 1. Click ▼ next to *Coupon Service*. The feed ID `couponing` will be exposed. > 1. Hover over the feed ID and select the duplicate icon ( > ). > 1. Select a field in your message content and paste the copied data. You should see: > ```text > {{#feed "couponing"}} > {{/feed}} > ``` ### Rendering as a barcode Use the [JsBarcode library](https://github.com/lindell/JsBarcode) to render your alphanumeric codes as barcodes. Make sure that the location where you are using a barcode supports HTML and JavaScript. For example, you cannot use barcodes in push notifications since they only support plain text. **Sample content for a barcode** ```html
``` ### Displaying an alternative coupon code If your use case requires two versions of a coupon code such as an in-store and an online version, you can include both versions in your CSV file and display them in your message. **Content format for alternative code** ```text {{#feed "couponing"}} In-store code {{coupon_code}} or an online code {{coupon_code_alt}} {{/feed}} ``` ### Specifying the campaign ID and feed failure behavior Specifying the campaign ID and determining feed failure handling differs between the dashboard and API. In the dashboard, you set both in the Delivery step of a composer in *External data feed options*: * **Default value for campaign** — Enter the campaign ID (not the campaign name) for the campaign you want to use for the message. The default value is the first campaign created for the project. * **Failure behavior** — Select *Abort sending the message* or *Send message without this data*. For most Coupons scenarios, you likely want to use *Abort...* since the message won't make sense without a code and, potentially, its validity period.. Also consider how ongoing (automated and recurring) messages will be affected if you later [delete their campaigns](#deleting-a-campaign). See also: [Success, Retry, and Error Conditions](#success-retry-and-error-conditions). ![Configuring a Coupons feed in the composer](https://www.airship.com/docs/images/coupons-delivery_hu_f6cfb36382f70974.webp) *Configuring a Coupons feed in the composer* --- With the API, you set the campaign ID and failure (error) behavior in the [External Data Feed References Object](https://www.airship.com/docs/developer/rest-api/ua/schemas/external-data-feeds-references/). When using a Coupons feed you must include a [`templates` object](https://www.airship.com/docs/developer/rest-api/ua/operations/personalization/) in the payload or set the `personalization` option to `true`. * The value for `name` is `couponing` for all Coupons campaigns. * The possible values for `on_error` are `cancel` and `continue`. These are equivalent to the dashboard options *Abort sending the message* and *Send message without this data*. `continue` will result in sending the message despite an error. Otherwise, a feed failure will prevent Airship from sending your message. * To specify the campaign ID, you must include a `defaults` object `couponing` with property `campaign` and the campaign ID as its value. **Example push request using a Coupons feed** ```http POST /api/push HTTP/1.1 Authorization: Basic Accept: application/vnd.urbanairship+json; version=3 Content-Type: application/json { "device_types":[ "ios" ], "audience":{ "tag": "earlyBirds" }, "notification":{ "alert":"{{#feed \"couponing\"}} As a token of our appreciation for being a loyal Fan, please enjoy 15% off your next in-app order. Offer expires {{dateFormat campaign_end_date locale="en-US" format="SHORT"}} Use coupon code: {{coupon_code}}{{/feed}}" }, "options": { "personalization": true }, "feed_references": { "feeds": [ { "name": "couponing", "params": { }, "on_error": "cancel" } ], "defaults": { "couponing": { "campaign": "2023loyalty" } } } } ``` ## Managing campaigns and adding more codes To see a list of all your campaigns and their current number of available coupons.: 1. Next to your project name, select the dropdown menu (▼), then **Settings**. 1. Under **Project settings**, select **Coupons**. Campaigns are sorted by effective date and time, latest first. Select the edit icon ( ) to edit the following for a campaign: * Name * Expiration date and time * Remaining codes contact — Addresses entered here will replace those entered for every campaign in the project. * [CSV file](#formatting-your-csv-file) — You can upload a new file only. You cannot download previously uploaded data. Generally, you only need to upload a new CSV file when you want to **add more codes** to a campaign. You can either append rows to the original CSV file used to create the campaign and reupload it or create a new CSV file containing only rows with new codes. If creating a new file, the only required headers are `coupon_code` and `campaign`, even if a previously uploaded file contained additional headers. You also have the option to provide a new CSV file via SFTP. Understand the differences in handling before deciding on a method: * If you upload a new CSV file using the dashboard, only `coupon_code` values will be processed. All other columns will be ignored. * If you upload a new CSV file via SFTP, all values (other than for `campaign`) will update the current campaign settings in the dashboard. For this reason, always verify that your CSV file has the correct `campaign` value for the campaign you intend to update. To upload a new CSV file via SFTP, see: [SFTP upload for CSV files](https://www.airship.com/docs/guides/audience/segmentation/sftp-upload/). > **Note:** Since message content is populated at send time, if you upload a new CSV file for a campaign, scheduled messages will use the most recent campaign data. They won't necessarily use the values available when the message was created. "Scheduled" includes recurring messages. ### Deleting a campaign > **Warning:** If you delete a campaign, any messages content referencing its data will be handled according to the messages's [feed failure behavior setting](#specifying-the-campaign-id-and-feed-failure-behavior). 1. Next to your project name, select the dropdown menu (▼), then **Settings**. 1. Under **Project settings**, select **Coupons**. 1. Select the delete icon (trash) for a campaign. ## Success, Retry, and Error conditions

Airship considers any 2xx response from a feed to be a success and will continue a message using the feed response. Airship considers any 3xx, 4xx, and 5xx codes (except 502/503) to be error conditions.

HTTP status Result Behavior
2xx Success Airship uses the feed response, even if empty.
3xx Error Error behavior determined by user.
4xx Error Error behavior determined by user.
5xx Error Error behavior determined by user.
timeout (60 sec) Retry Up to 4 retries, 60 sec between attempts.
502/503 Retry Up to 4 retries, 60 sec between attempts.