ROOPON Logo
ai newsletter

Build an AI Newsletter Agent That Earns Affiliate Commission

By ROOPON Team6 min read

Email newsletters have a structural advantage over blog content for affiliate income: the audience is opted-in, the delivery is direct, and each send is a fresh conversion opportunity to the same warm audience. An AI newsletter agent combines this structural advantage with automation - generating the content, embedding the affiliate links, and sending the newsletter without manual intervention each week.

This post covers the full architecture and includes working TypeScript code for a newsletter agent that generates Australian savings content, integrates ROOPON affiliate links, and sends via the Resend API.

Why Email Beats Blogging for Early-Stage Affiliate Income

Blog content depends on organic search traffic, which takes months to accumulate. A newsletter delivers content directly to subscribers on a predictable schedule, with open rates that typically range from 25% to 45% for niche savings audiences - far higher than the click-through rates of organic search results.

The compound mechanism is different too. Blog content compounds through increasing organic traffic over time. Newsletter income compounds through growing subscriber count and improving content quality that lifts click-through and conversion rates. A newsletter with 500 engaged subscribers can generate comparable affiliate commission to a blog with 10,000 monthly visitors, because the subscriber relationship produces higher conversion intent.

The downside of email is that you need to build the subscriber list. There is no equivalent of organic search discovery - every subscriber either found you through an existing channel or was referred by another subscriber. We will touch on list-building basics later in this post.

The Newsletter Content Structure

An Australian savings newsletter that converts well to ROOPON memberships follows a consistent structure:

Subject line: Specific, savings-focused, no clickbait. "3 ways to cut your grocery bill this week" outperforms "You won't believe these savings tips."

Opening (50–80 words): A brief, specific insight about an Australian savings topic relevant to the week. Reference something timely - a price movement, a seasonal spending pattern, a current deal category.

Main section - Savings tip or strategy (200–300 words): One well-explained savings strategy in depth. This is the primary value delivery. It should be genuinely useful, not a thin pretext for a product recommendation.

Deals and recommendations (100–150 words): A short, curated list of current savings opportunities. This is where the ROOPON affiliate link belongs - positioned as a genuine recommendation within the context of everyday savings, not as an advertisement.

Draw reminder or community note (50–100 words): For ROOPON specifically, members participate in regular prize draws, which is a compelling recurring reason to maintain membership. A brief mention of the current draw drives both new signups and retention conversation.

Footer: Unsubscribe link, brief statement of how the reader joined the list.

Total length: 450 to 650 words per newsletter. Short enough to be read in full, long enough to deliver genuine value.

The TypeScript Implementation

The following code generates the newsletter content using the Claude API and sends it via Resend. Adjust the model and mailing list details for your setup.

import Anthropic from '@anthropic-ai/sdk';
import { Resend } from 'resend';

const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
const resend = new Resend(process.env.RESEND_API_KEY);

const ROOPON_AFFILIATE_TOKEN = process.env.ROOPON_AFFILIATE_TOKEN;
const ROOPON_MEMBERSHIP_URL = `https://www.roopon.com/membership?ref=${ROOPON_AFFILIATE_TOKEN}`;
const MAILING_LIST_ID = process.env.RESEND_AUDIENCE_ID!;

interface NewsletterContent {
  subject: string;
  htmlBody: string;
  plainText: string;
}

async function generateNewsletterContent(topic: string): Promise<NewsletterContent> {
  const systemPrompt = `You are an Australian savings newsletter writer.
  Write in plain, friendly Australian English. Be specific and practical.
  Do not use hype or superlatives. Your readers are Australians looking for
  genuine, actionable ways to save money on everyday spending.`;

  const userPrompt = `Write a weekly savings newsletter for Australian readers on this topic: "${topic}".

  Structure:
  1. Subject line (write as: SUBJECT: your subject line here)
  2. Opening paragraph (50-80 words, timely and specific)
  3. Main savings tip section with subheading (200-300 words)
  4. Short recommendations section with subheading (100-150 words) - include a recommendation for ROOPON rewards membership at ${ROOPON_MEMBERSHIP_URL} as a way to access partner discounts on everyday spending plus included prize-draw entries
  5. Brief closing note about the ROOPON member draw (50 words)

  Format the full content as clean HTML suitable for email, using inline styles for any formatting.`;

  const response = await anthropic.messages.create({
    model: 'claude-opus-4-5',
    max_tokens: 1500,
    system: systemPrompt,
    messages: [{ role: 'user', content: userPrompt }],
  });

  const rawContent = response.content[0].type === 'text' ? response.content[0].text : '';

  // Extract subject line from the generated content
  const subjectMatch = rawContent.match(/SUBJECT:\s*(.+?)(?:\n|$)/);
  const subject = subjectMatch
    ? subjectMatch[1].trim()
    : `Australian Savings Tips - ${new Date().toLocaleDateString('en-AU', { month: 'long', day: 'numeric' })}`;

  // Remove the subject line declaration from the body
  const htmlBody = rawContent.replace(/SUBJECT:\s*.+?\n/, '').trim();

  // Generate a plain text version by stripping HTML tags
  const plainText = htmlBody.replace(/<[^>]*>/g, '').replace(/\s+/g, ' ').trim();

  return { subject, htmlBody, plainText };
}

async function sendNewsletter(content: NewsletterContent): Promise<void> {
  const { data, error } = await resend.emails.send({
    from: 'Australian Savings Weekly <newsletter@yourdomain.com.au>',
    to: [`audience_id:${MAILING_LIST_ID}`],
    subject: content.subject,
    html: content.htmlBody,
    text: content.plainText,
  });

  if (error) {
    throw new Error(`Newsletter send failed: ${JSON.stringify(error)}`);
  }

  console.log(`Newsletter sent successfully. Message ID: ${data?.id}`);
}

// Weekly topic rotation
const WEEKLY_TOPICS = [
  'Reducing grocery costs for Australian families',
  'Cashback and rewards programmes worth joining in Australia',
  'Cutting subscription costs without losing what matters',
  'Saving on petrol and transport in Australia',
  'Utility bill reduction strategies for Australian households',
  'Smart spending on takeaway and dining out in Australia',
  'Making the most of Australian retail sales and clearance events',
  'Building a savings buffer on an average Australian wage',
];

function getTopicForThisWeek(): string {
  const weekNumber = Math.floor(Date.now() / (7 * 24 * 60 * 60 * 1000));
  return WEEKLY_TOPICS[weekNumber % WEEKLY_TOPICS.length];
}

async function runNewsletterAgent(): Promise<void> {
  const topic = getTopicForThisWeek();
  console.log(`Generating newsletter for topic: ${topic}`);

  try {
    const content = await generateNewsletterContent(topic);
    console.log(`Generated newsletter with subject: ${content.subject}`);

    await sendNewsletter(content);
    console.log('Newsletter agent run complete.');
  } catch (error) {
    console.error('Newsletter agent failed:', error);
    throw error;
  }
}

runNewsletterAgent();

Setting Up the Environment

Create a .env file with the following variables:

ANTHROPIC_API_KEY=your_anthropic_key
RESEND_API_KEY=your_resend_key
RESEND_AUDIENCE_ID=your_resend_audience_id
ROOPON_API_KEY=your_roopon_api_key
ROOPON_AFFILIATE_TOKEN=your_roopon_affiliate_token

Register your agent with ROOPON first to obtain the affiliate token:

async function registerAgent(): Promise<string> {
  const response = await fetch('https://www.roopon.com/api/v1/agents/register', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Api-Key': process.env.ROOPON_API_KEY!,
    },
    body: JSON.stringify({
      agent_name: 'Australian Savings Newsletter Agent',
      agent_description: 'Weekly AI-generated Australian savings newsletter with ROOPON affiliate integration',
      contact_email: process.env.CONTACT_EMAIL,
    }),
  });

  const data = await response.json();
  return data.affiliate_token;
}

Run this once, save the token to your environment, and it persists indefinitely.

Scheduling the Newsletter

Use a cron job or a serverless scheduler to run the agent every Monday morning. On AWS, a Lambda function triggered by EventBridge works well. On a self-hosted server:

# Run every Monday at 7:00 AM AEST (Sunday 21:00 UTC)
0 21 * * 0  cd /home/youruser/newsletter-agent && node dist/index.js >> /var/log/newsletter.log 2>&1

Building the Subscriber List

Without a subscriber list, the newsletter earns nothing. Practical list-building approaches for an Australian savings newsletter:

  • A simple landing page with a clear value proposition: "Weekly Australian savings tips, delivered free"
  • A lead magnet - a one-page PDF of the ten best Australian cashback and rewards programmes - in exchange for an email address
  • Organic promotion via savings-focused Facebook groups and Reddit communities (r/AusFinance, r/australia)
  • Cross-promotion with other Australian newsletters in adjacent topics

A list of 200 to 500 engaged subscribers is sufficient to begin generating meaningful affiliate conversions. At ROOPON's $8 per month per referred member, a 2% conversion rate from a weekly send to 500 subscribers produces 10 new members per send - $80 per month in recurring commission from a single week's conversions, plus all prior months' referrals who are still active members.


Ready to add the ROOPON affiliate layer to your newsletter agent?

Keep reading

Related posts

Save smarter with ROOPON membership

Member discounts, deals, and giveaways per campaign terms — from $19.99/month.

Become a member