Automate Your Marketing Links with Dub.co and Node.js
If you're running campaigns across email, social, and paid ads, you know the pain: dozens of links to create, tag, and track. Most teams do this manually in Bitly's dashboard, one link at a time. That's slow, error-prone, and doesn't scale.
I built a Node.js script that automates the entire workflow using the Dub.co API. It creates branded short links in bulk, applies UTM parameters automatically, and pipes click data into a dashboard. The whole thing took an afternoon.
Here's how to build it.
Why Automate Link Management?
Manual link creation breaks down fast:
- Campaign launches need 10-20 links (one per channel, variant, audience segment)
- UTM parameters get inconsistent when humans type them
- Tracking requires checking each link individually
- Reporting means exporting CSVs from multiple dashboards
Automation fixes all of this. One script, one command, consistent links with analytics built in.
Why Dub.co Over Bitly for Automation
I tried automating with Bitly first. Two problems:
- Rate limits on the free tier are brutal — 100 links/month, and the API is throttled
- Custom domains cost $35/month — just to use your own brand in links
Dub.co gives you:
- Free custom domains
- 1,000 links/month on the free plan
- Clean REST API with no weird auth hoops
- Built-in analytics (geo, device, referrer)
- Free QR code generation per link
For automation, the generous free tier is what matters most. You can actually build something useful without hitting a paywall.
Step 1: Set Up Your Dub.co Workspace
Sign up at Dub.co and grab your API key from the dashboard under Settings → API Keys.
Optional but recommended: add a custom domain. Go to Settings → Domains, add something like go.yoursite.com, and point a CNAME record to clink.dub.co. This takes 2 minutes and makes every link you create on-brand.
Step 2: Install Dependencies
mkdir link-automator && cd link-automator
npm init -y
npm install node-fetch dotenv
Create a .env file:
DUB_API_KEY=your_api_key_here
DUB_DOMAIN=go.yoursite.com
Step 3: Build the Link Creator
Here's the core script that creates branded links with UTM parameters:
// create-links.js
import fetch from 'node-fetch';
import { config } from 'dotenv';
config();
const DUB_API = 'https://api.dub.co';
const headers = {
'Authorization': `Bearer ${process.env.DUB_API_KEY}`,
'Content-Type': 'application/json'
};
async function createLink({ url, slug, campaign, channel }) {
// Append UTM parameters automatically
const targetUrl = new URL(url);
targetUrl.searchParams.set('utm_source', channel);
targetUrl.searchParams.set('utm_medium', 'link');
targetUrl.searchParams.set('utm_campaign', campaign);
const response = await fetch(`${DUB_API}/links`, {
method: 'POST',
headers,
body: JSON.stringify({
url: targetUrl.toString(),
domain: process.env.DUB_DOMAIN || 'dub.sh',
key: slug,
externalId: `${campaign}-${channel}`,
tagIds: [] // add tag IDs from your workspace
})
});
const data = await response.json();
return data;
}
// Define your campaign links
const campaignLinks = [
{ url: 'https://yoursite.com/launch', slug: 'launch-tw', campaign: 'spring-launch', channel: 'twitter' },
{ url: 'https://yoursite.com/launch', slug: 'launch-em', campaign: 'spring-launch', channel: 'email' },
{ url: 'https://yoursite.com/launch', slug: 'launch-li', campaign: 'spring-launch', channel: 'linkedin' },
{ url: 'https://yoursite.com/launch', slug: 'launch-rd', campaign: 'spring-launch', channel: 'reddit' },
{ url: 'https://yoursite.com/launch', slug: 'launch-yt', campaign: 'spring-launch', channel: 'youtube' },
];
async function main() {
console.log(`Creating ${campaignLinks.length} links...\n`);
for (const link of campaignLinks) {
try {
const result = await createLink(link);
console.log(`✓ ${link.channel}: https://${result.domain}/${result.key}`);
} catch (err) {
console.error(`✗ ${link.channel}: ${err.message}`);
}
}
}
main();
Run it:
node create-links.js
Output:
Creating 5 links...
✓ twitter: https://go.yoursite.com/launch-tw
✓ email: https://go.yoursite.com/launch-em
✓ linkedin: https://go.yoursite.com/launch-li
✓ reddit: https://go.yoursite.com/launch-rd
✓ youtube: https://go.yoursite.com/launch-yt
Five branded, tracked links in under a second. Each one has consistent UTM parameters and an externalId you can use to query analytics later.
Step 4: Pull Click Analytics
Now the powerful part — pulling click data programmatically:
// analytics.js
import fetch from 'node-fetch';
import { config } from 'dotenv';
config();
const headers = {
'Authorization': `Bearer ${process.env.DUB_API_KEY}`,
};
async function getCampaignStats(campaign) {
// Get all links for this campaign
const linksRes = await fetch(
`https://api.dub.co/links?search=${campaign}`,
{ headers }
);
const links = await linksRes.json();
console.log(`\n--- Campaign: ${campaign} ---`);
console.log(`Total links: ${links.length}\n`);
let totalClicks = 0;
for (const link of links) {
const clicks = link.clicks || 0;
totalClicks += clicks;
// Get detailed analytics for high-performing links
if (clicks > 0) {
const analyticsRes = await fetch(
`https://api.dub.co/analytics?linkId=${link.id}&event=clicks&groupBy=countries`,
{ headers }
);
const geo = await analyticsRes.json();
console.log(`${link.key}: ${clicks} clicks`);
console.log(` Top countries: ${geo.slice(0, 3).map(g => `${g.country} (${g.clicks})`).join(', ')}`);
} else {
console.log(`${link.key}: 0 clicks`);
}
}
console.log(`\nTotal campaign clicks: ${totalClicks}`);
}
getCampaignStats('spring-launch');
This gives you per-link and per-country breakdowns without ever opening a dashboard.
Step 5: Schedule It with Cron
For ongoing campaigns, schedule the analytics script to run daily and send you a summary:
// daily-report.js
import fetch from 'node-fetch';
import { config } from 'dotenv';
config();
async function dailyReport() {
const headers = {
'Authorization': `Bearer ${process.env.DUB_API_KEY}`,
};
// Get analytics for the last 24 hours
const res = await fetch(
'https://api.dub.co/analytics?event=clicks&groupBy=top_links&interval=24h',
{ headers }
);
const topLinks = await res.json();
const report = topLinks
.filter(l => l.clicks > 0)
.map(l => `${l.link.key}: ${l.clicks} clicks`)
.join('\n');
console.log(`Daily Link Report (${new Date().toLocaleDateString()})`);
console.log('='.repeat(40));
console.log(report || 'No clicks in the last 24 hours');
// Pipe this to Slack, email, or wherever you want
}
dailyReport();
Add it to your crontab:
# Run daily at 9 AM
0 9 * * * cd /path/to/link-automator && node daily-report.js >> /var/log/link-report.log
The Bigger Picture
Once your links are created programmatically, you can:
- A/B test landing pages by creating multiple links to different URLs and comparing click-through rates
- Auto-expire links for time-limited promos using Dub.co's expiration feature
- Generate QR codes for each link (Dub.co creates them automatically)
- Tag links by team member so everyone can see which channels perform
The API supports all of this. Check the Dub.co docs for the full reference.
Wrapping Up
Link management shouldn't be manual work. With Dub.co and a simple Node.js script, you can create, track, and report on hundreds of links without touching a dashboard.
The free tier gives you everything you need to get started — custom domains, analytics, and a clean API. If you're still creating links one by one in Bitly, give Dub.co a shot. Your future self will thank you.
Have questions about the setup? Drop a comment and I'll help you out.
Top comments (0)