// Name: Customer.io Top Clicked Links // Description: Get the top clicked links for my AI Dev Essentials newsletter // You can find the newsletter here: // https://egghead.io/newsletters/ai-dev-essentials import "@johnlindquist/kit" // Prompt (or load) your Customer.io App (not tracking) API key const API_KEY = await env('CIO_API_KEY') // Customer.io App API base (US region). Remove the extra /api segment const API_BASE = 'https://api.customer.io/v1' /** * Shape of each newsletter returned by the `/newsletters` endpoint. */ interface NewsletterSummary { id: number name: string // Allow unknown extra properties without failing type-checking [key: string]: unknown } let newsletters: NewsletterSummary[] = [] try { // We want one-off newsletters, not API-triggered broadcasts, so we hit /newsletters const listResp = await get(`${API_BASE}/newsletters`, { headers: { Authorization: `Bearer ${API_KEY}` }, }) // The API might place the array under different keys, so check each option in order if (Array.isArray(listResp.data.newsletters)) { newsletters = listResp.data.newsletters } else if (Array.isArray(listResp.data.broadcasts)) { newsletters = listResp.data.broadcasts } else if (Array.isArray(listResp.data.items)) { newsletters = listResp.data.items } else if (Array.isArray(listResp.data.campaigns)) { newsletters = listResp.data.campaigns } else if (Array.isArray(listResp.data)) { newsletters = listResp.data } if (newsletters.length === 0) { await div(md('No newsletters found for this workspace.')) exit(0) } } catch (err) { console.error('Error fetching newsletter list:', err) await div(md('❌ Failed to fetch newsletters. Check your API key, region, or network.')) exit(1) } // ---------- 2) Let the user choose a newsletter ---------- const choiceItems: { name: string description: string value: NewsletterSummary }[] = [] for (const nl of newsletters) { const displayName = nl.name ? String(nl.name) : `Newsletter ${nl.id}` if (nl.name.includes('AI Dev Essentials')) { choiceItems.push({ name: displayName, description: `ID: ${nl.id}`, value: nl }) } } //sort choiceItems by id descending choiceItems.sort((a, b) => { return b.value.id - a.value.id }) const selectedNewsletter = await arg( { placeholder: 'Select a newsletter to inspect', hint: 'Customer.io newsletters', strict: true, }, choiceItems ) const newsletterId = selectedNewsletter.id // ---------- 3) Fetch link-click metrics for the chosen newsletter ---------- const params = { period: 'days', steps: 30, // last 30 days unique: 'false', // total clicks (not unique) } try { const metricsResp = await get(`${API_BASE}/newsletters/${newsletterId}/metrics/links`, { headers: { Authorization: `Bearer ${API_KEY}` }, params: params, }) const linkMetrics = metricsResp.data.links ?? [] if (!Array.isArray(linkMetrics) || linkMetrics.length === 0) { await div(md(`No link click data available for **${selectedNewsletter.name}**.`)) exit(0) } // Uncomment the line below to inspect the raw API response in an editor window // await editor(JSON.stringify(linkMetrics, null, 2)) // -------- 4) Transform raw metrics into a friendlier structure -------- interface LinkClickSummary { url: string clicks: number } const summaries: LinkClickSummary[] = [] for (const metricItem of linkMetrics) { // Extract the URL let url = 'undefined' if (metricItem.link && metricItem.link.href) { url = String(metricItem.link.href) } else if (metricItem.url) { url = String(metricItem.url) } else if (metricItem.href) { url = String(metricItem.href) } // Sum daily clicks let totalClicks = 0 const dailyArray: number[] = metricItem.metric?.series?.clicked ?? [] if (Array.isArray(dailyArray)) { for (const n of dailyArray) { totalClicks += n } } summaries.push({ url, clicks: totalClicks }) } // Sort links by total clicks descending summaries.sort((a, b) => b.clicks - a.clicks) // Prepare a Markdown table of top links (limit to top 10) const topLinks = summaries.slice(0, 10) const tableLines: string[] = ['| Rank | URL | Clicks |', '|-----:|-----|-------:|'] for (let i = 0; i < topLinks.length; i++) { const l = topLinks[i] tableLines.push(`| ${i + 1} | ${l.url} | ${l.clicks} |`) } await div(md(`## Top Clicked Links for **${selectedNewsletter.name}**\n\n${tableLines.join('\n')}`)) } catch (err) { console.error('Error fetching link metrics:', err) await div(md('❌ Failed to fetch link metrics.')) exit(1) }