> ## Documentation Index
> Fetch the complete documentation index at: https://help.retainful.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Shortcodes

> The full reference for the shortcodes that personalize your emails — contact details, store info, cart links, coupons, and order summaries.

Shortcodes personalize your emails. Write `{{ contact.firstName }}` once, and every recipient sees their own name.

```liquid theme={null}
Hi {{ contact.firstName | default: 'there' }}, thanks for shopping with {{ shop_name }}!
```

Renders as: **Hi Ada, thanks for shopping with Acme Supply Co!**

This page is the full syntax reference. To insert a tag from the editor toolbar instead of writing it by hand, see [Personalization](/campaigns/personalization).

## Where each group works

Not every shortcode works in every email. Using one outside its context leaves it blank rather than raising an error.

| Group                                 | Broadcasts                   | Automations                   |
| ------------------------------------- | ---------------------------- | ----------------------------- |
| [Customer details](#customer-details) | Yes                          | Yes                           |
| [Store](#store)                       | Body only, not subject lines | Yes                           |
| [Abandoned cart](#abandoned-cart)     | No                           | Abandoned-cart flows only     |
| [Coupon](#coupon)                     | No                           | Flows with a coupon step only |
| [Summary block](#summary-block)       | No                           | Yes                           |
| [Unsubscribe](#unsubscribe)           | Yes                          | Yes                           |

## Customer details

Works in every email — broadcasts and automations.

| Shortcode                  | Shows             | Example             |
| -------------------------- | ----------------- | ------------------- |
| `{{ contact.firstName }}`  | First name        | `Ada`               |
| `{{ contact.lastName }}`   | Last name         | `Lovelace`          |
| `{{ contact.fullName }}`   | Full name         | `Ada Lovelace`      |
| `{{ contact.email }}`      | Email address     | `ada@example.com`   |
| `{{ contact.phone }}`      | Phone number      | `+1 (555) 123-4567` |
| `{{ contact.address1 }}`   | Address line 1    | `123 Market St`     |
| `{{ contact.address2 }}`   | Address line 2    | `Apt 4B`            |
| `{{ contact.city }}`       | City              | `London`            |
| `{{ contact.state }}`      | State / province  | `Greater London`    |
| `{{ contact.postalCode }}` | ZIP / postal code | `12345`             |
| `{{ contact.country }}`    | Country           | `United Kingdom`    |

```liquid theme={null}
Hi {{ contact.firstName | default: 'there' }},
```

Renders as `Hi Ada,` — or `Hi there,` for a contact with no first name.

* **Always add `| default:`.** Most contacts have only an email address, so without a fallback your email opens `Hi ,`.
* **Always write `contact.` in front.** `{{ firstName }}` on its own silently fails in broadcast emails.
* `{{ contact.fullName }}` shows the **email address** when both names are blank. For greetings, `{{ contact.firstName | default: 'there' }}` is safer.

## Store

Works in every email.

| Shortcode                                   | Shows                           | Example                            |
| ------------------------------------------- | ------------------------------- | ---------------------------------- |
| `{{ shop_name }}`                           | Your store name                 | `Acme Supply Co`                   |
| `{{ shop_email }}`                          | Your store email                | `orders@acme.com`                  |
| `{{ shop_address }}`                        | Your store address              | `123 Market St`                    |
| `{{ retainful_shop_url }}`                  | Link to your store              | `https://acme.com`                 |
| `{{ retainful_shop_url_with_coupon_code }}` | Store link, coupon auto-applied | `https://acme.com/discount/SAVE10` |

```liquid theme={null}
Thanks for shopping with {{ shop_name }}!
<a href="{{ retainful_shop_url }}">Visit our store</a>
```

* **In broadcasts these work in the email body but not in the subject line.** In automations they work in both.
* `{{ retainful_shop_url_with_coupon_code }}` requires a coupon step in the workflow. In a broadcast it drops the coupon silently and links to your store.

## Abandoned cart

<Warning>
  Abandoned-cart automations only. In any other email these render blank.
</Warning>

| Shortcode                                       | Shows                          |
| ----------------------------------------------- | ------------------------------ |
| `{{ abandoned_checkout_url }}`                  | Link back to their cart        |
| `{{ abandoned_checkout_url_with_coupon_code }}` | Cart link, coupon auto-applied |

```liquid theme={null}
{% if abandoned_checkout_url.size > 0 %}
  <a href="{{ abandoned_checkout_url }}">Complete your order</a>
{% endif %}
```

* **Keep the `.size > 0` check.** Without it, an email with no cart still shows the button, linking nowhere. See [Hide empty values correctly](#hide-empty-values-correctly).
* The coupon version also requires a coupon step in the workflow.

## Coupon

<Warning>
  Automations with a coupon step only. These never work in broadcasts.
</Warning>

| Shortcode                            | Shows                | Example                           |
| ------------------------------------ | -------------------- | --------------------------------- |
| `{{ coupon.code }}`                  | The discount code    | `SAVE10XYZAB`                     |
| `{{ retainful_coupon_expiry_date }}` | When it expires      | Use with the `date` filter, below |
| `{{ coupon.expiration_text }}`       | How long it is valid | `7 days`                          |
| `{{ coupon.expiration_days }}`       | Days until expiry    | `7`                               |

```liquid theme={null}
{% if coupon.code.size > 0 %}
  Your code: <strong>{{ coupon.code }}</strong>
  Valid for {{ coupon.expiration_text }}.
  Expires {{ retainful_coupon_expiry_date | date: "%B %d, %Y" }}
{% endif %}
```

Renders as: Your code: `SAVE10XYZAB`. Valid for 7 days. Expires March 05, 2026

* Each contact receives their own unique code.
* **Always apply `| date:` to `{{ retainful_coupon_expiry_date }}`.** On its own it prints a raw timestamp.
* The expiry clock starts when the coupon is **created**, so every email in a sequence shows the same date.
* **Keep the `.size > 0` check.** With no coupon step, `{{ coupon.expiration_days }}` prints `0` and the rest print blank, so the email reads "Valid for . (0 days)".

## Summary block

Lists the items your customer ordered or left in their cart. Drag the block in and the editor writes the loop for you — you only edit what is inside it.

<Warning>
  Automation templates only. The Summary block is not available in broadcast campaigns.
</Warning>

| Field                  | Shows                         | Example                 |
| ---------------------- | ----------------------------- | ----------------------- |
| `{{ item.name }}`      | Product name                  | `Classic White T-Shirt` |
| `{{ item.quantity }}`  | Quantity                      | `2`                     |
| `{{ item.price }}`     | Line total (price × quantity) | `$59.98`                |
| `{{ item.unitPrice }}` | Price for one                 | `$29.99`                |
| `{{ item.imageUrl }}`  | Product image                 | `https://.../shirt.jpg` |

Stripped of its layout tables and Outlook-only comments, a default Summary block is:

```liquid theme={null}
<!-- htmlmin:ignore -->
{% for item in event.line_items limit:9999 %}
<!-- htmlmin:ignore -->

  <img src="{{item.imageUrl}}" width="230">
  <div>{{item.name}} × {{item.quantity}}</div>
  <div>{{item.price}}</div>

<!-- htmlmin:ignore -->
{% endfor %}
<!-- htmlmin:ignore -->
```

This produces one entry per item: an image, then `Classic White T-Shirt × 2`, then `$59.98`.

<Warning>
  `{{ item.price }}` is already the line total — never multiply it. `{{ item.price | times: item.quantity }}` counts the quantity twice: a 2 × `$29.99` line shows `$119.96` instead of `$59.98`. For "unit × qty = total", write `{{ item.unitPrice }} × {{ item.quantity }} = {{ item.price }}`.
</Warning>

* **Show or hide the name and price**, and set their colour and size, in the block's settings panel. You do not need to touch the shortcodes.
* **The Review block is the same thing with a button**, but its loop variable is `line`, not `item` — write `{{ line.name }}` there.
* **If you edit the source:** keep the `htmlmin:ignore` HTML comments shown above (they stop the loop tags being mangled), do not rename `item`, and do not change `event.line_items`. `limit:9999` means no limit — lower it to cap how many items show.
* **In preview it shows placeholder text** — `Product Title`, `Quantity`, `$xx.xx`, and a grey image. Real values appear on send.

## Unsubscribe

Works in every email.

| Shortcode                             | Shows                     |
| ------------------------------------- | ------------------------- |
| `{{ unsubscribe_shop_customer_url }}` | Personal unsubscribe link |

```liquid theme={null}
<a href="{{ unsubscribe_shop_customer_url }}">Unsubscribe</a>
```

Unique per contact, and legally required in marketing email. If your email has no unsubscribe link, Retainful adds one automatically — but placing it yourself looks better. See [Deliverability best practices](/email-setup/deliverability).

## Four rules worth knowing

### Always add a fallback

```liquid theme={null}
Hi {{ contact.firstName | default: 'there' }},
```

`default` covers every "no value" case — blank, never set, or missing.

| Contact's first name | Output    |
| -------------------- | --------- |
| `Ada`                | Hi Ada,   |
| *(blank)*            | Hi there, |
| *(never set)*        | Hi there, |

### Hide empty values correctly

To hide a block when a shortcode has no value, test `.size > 0`:

**Correct:**

```liquid theme={null}
{% if coupon.code.size > 0 %}...{% endif %}
```

**Incorrect — the block always shows:**

```liquid theme={null}
{% if coupon.code %}...{% endif %}
```

The plain form looks right but does not work. An empty value is still "something" as far as Liquid is concerned, so the block renders anyway, with a blank inside it. That is what produces an anchor tag with an empty `href` — a real button that goes nowhere.

`.size > 0` is safe on every shortcode in this document.

### Typos are invisible

`{{ contact.frstName }}` renders as empty space. No error, no warning. Proofread, and always send a test email.

<Note>
  A test proves your *spelling* is right, not that the data will *be there*. A test send fills in sample values even for shortcodes that come back blank on a real send.
</Note>

### Do not invent tags

Only use what is in this document. A made-up `{% tag %}` — such as `{% current_year %}` — **breaks the whole email**: every shortcode disappears and the raw tag is shown to your customer.

For the current year, use `{{ "now" | date: "%Y" }}`.
