Step 1 of 4Make the campaign link with UTM tags on the destination

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.

  • Recipe
  • ~ 15 minutes
  • Android
  • iPhone
On this page 6
  1. 1. Make the campaign link with UTM tags on the destination
  2. 2. Check what a tap does before you send
  3. 3. Put the link in the email
  4. 4. Read the taps per channel
  5. Common errors
  6. Next steps
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.

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.

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

Terminal
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, plus the dry run from step 2:

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 runs the same decision as a real tap, without counting a tap or firing a webhook. Try an Android phone and an iPhone:

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

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

Terminal
curl "https://strait.link/v1/analytics?link=diwali-email&group=channel" \
  -H "Authorization: Bearer $SECRET_KEY"
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}; 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