# Email campaign links that open your app

> After this guide, every button in your campaign email is a Strait link that opens the right screen in your app, and each tap is counted under your email channel and campaign.

- Time: About 15 minutes
- You need: A Strait secret key in your server's environment as SECRET_KEY, and the email tool you send campaigns with
- Works on: Any server. Code for curl and Node 18+. The links work for Android and iPhone users and on computers.

Source: https://straitlink.in/docs/recipes/email-campaign-links/

An email campaign link needs two things: the destination, with your UTM tags on it, and a short link that opens the app. Strait reads `utm_source` and `utm_campaign` from the destination to label each tap, and sends the same URL, tags included, to your website or app. You don't add anything to the email itself.

## 1. Make the campaign link with UTM tags on the destination

Put the tags on `longUrl`, not on the short link. The destination is what your website analytics and your app see:

```bash
curl https://strait.link/v1/links \
  -H "Authorization: Bearer $SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "diwali-email",
    "longUrl": "https://shop.example.com/sale/diwali?utm_source=newsletter&utm_medium=email&utm_campaign=diwali-2026",
    "title": "Diwali sale"
  }'
```

The same with the [Node SDK](https://straitlink.in/docs/sdks/node/), plus the dry run from step 2:

```js title="Node: email-links.mjs"
import { StraitClient } from '@straitlink/node';

const strait = new StraitClient({ apiKey: process.env.SECRET_KEY, baseUrl: 'https://strait.link' });

const link = await strait.createLink({
  slug: 'diwali-email',
  longUrl: 'https://shop.example.com/sale/diwali?utm_source=newsletter&utm_medium=email&utm_campaign=diwali-2026',
  title: 'Diwali sale',
});
console.log(link.shortUrl, link.campaign);

const tap = await strait.simulateTap({ url: link.shortUrl, ua: 'android' });
console.log(tap.decision, tap.sentTo);
```

You don't need to send `campaign`: it defaults to the destination's `utm_campaign`. To compare two buttons in one email, make one link per button and give each its own `utm_content`.

**You should see:** `"shortUrl": "https://hilltop.strait.link/diwali-email"` and `"campaign": "diwali-2026"` in the answer. The Node script prints `https://hilltop.strait.link/diwali-email diwali-2026`, then `redirect app_or_store`.

## 2. Check what a tap does before you send

[`GET /v1/simulate`](https://straitlink.in/docs/api/simulate-tap/) runs the same decision as a real tap, without counting a tap or firing a webhook. Try an Android phone and an iPhone:

```bash
curl "https://strait.link/v1/simulate?url=https%3A%2F%2Fhilltop.strait.link%2Fdiwali-email&ua=android" \
  -H "Authorization: Bearer $SECRET_KEY"

curl "https://strait.link/v1/simulate?url=https%3A%2F%2Fhilltop.strait.link%2Fdiwali-email&ua=iphone" \
  -H "Authorization: Bearer $SECRET_KEY"
```

The other presets are `ipad`, `desktop`, `instagram-ios`, `instagram-android`, `facebook-android`, `whatsapp-android` and `bot`, or pass any raw user agent.

**You should see:** for Android, `"decision": "redirect"`, `"sentTo": "app_or_store"` and a `location` that starts `intent://shop.example.com/sale/diwali?utm_source=newsletter&utm_medium=email&utm_campaign=diwali-2026&strait_click=…`, so the tags reach the app. For iPhone, `sentTo` follows your iPhone settings: a workspace with no App Store ID answers `"sentTo": "website"` with the destination, tags included, as `location`.

## 3. Put the link in the email

Paste `https://hilltop.strait.link/diwali-email` as the button's link in your email tool. If the tool adds its own UTM tags to every link, check what it adds: a `utm_source` on the tapped URL wins over the one on the destination for the tap's channel, and it isn't copied to the destination.

**You should see:** in a test send to yourself, the button's link goes to `https://hilltop.strait.link/diwali-email`, and tapping it on a phone with your app opens the sale screen.

## 4. Read the taps per channel

```bash
curl "https://strait.link/v1/analytics?link=diwali-email&group=channel" \
  -H "Authorization: Bearer $SECRET_KEY"
```

```js title="Node: email-report.mjs"
import { StraitClient } from '@straitlink/node';

const strait = new StraitClient({ apiKey: process.env.SECRET_KEY, baseUrl: 'https://strait.link' });

const report = await strait.getAnalytics({ link: 'diwali-email', group: 'channel' });
console.log(report.rows);
```

`group` also takes `day`, `link`, `platform` or `none`. **Dashboard → Analytics** shows the same numbers, with `diwali-2026` in the **Campaign** filter.

**You should see:** after your test tap, `"rows": [{ "key": "newsletter", "taps": 1, "billableTaps": 1, … }]`. App opens and installs join the same row once people open the app from the link.

## Common errors

- **`409` "slug already exists in this workspace".** Each campaign or button needs its own slug, like `diwali-email-hero` and `diwali-email-footer`.
- **Taps show up as `direct` or `gmail`, not `newsletter`.** The destination has no `utm_source`. Change it with [`PATCH /v1/links/{slug}`](https://straitlink.in/docs/api/update-link/); the short link in the email stays the same.
- **Your web analytics sees no UTM tags.** The tags were added to the short link (`https://hilltop.strait.link/diwali-email?utm_source=…`). Strait uses a tag there to label the tap, but doesn't copy it to the destination. Put the tags on `longUrl`.

## Next steps

- [Create links in bulk from your backend](https://straitlink.in/docs/recipes/batch-create-links/): one link per segment or per button, up to 500 per call.
- [Analytics: how we count](https://straitlink.in/docs/analytics/): what counts as a tap, an install and an open.
