The Newsletter Business Model
Financial newsletters are one of the most profitable content businesses. The key challenge is producing consistent, data-driven content on a schedule. By automating the data collection and initial analysis, you can focus on the editorial -- the insights that subscribers actually pay for.
This tutorial builds a system that: (1) pulls market data from the TickAtlas API, (2) generates a structured market brief, (3) formats it as an email, and (4) sends it via an email service.
Architecture
Cron Job (daily at 6:00 AM UTC)
|
v
Python Script:
1. Fetch indicators for watchlist (TickAtlas API)
2. Identify notable signals
3. Generate market summary
4. Format as HTML email
5. Send via SendGrid/Mailgun Step 1: Fetch Market Data
import requests
from dataclasses import dataclass, field
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://tickatlas.com/v1"
WATCHLIST = ["EURUSD", "GBPUSD", "USDJPY", "XAUUSD", "BTCUSD"]
@dataclass
class SymbolBrief:
symbol: str
close: float
rsi: float
macd_signal: str
trend: str
notable: bool = False
note: str = ""
def fetch_symbol_brief(symbol: str) -> SymbolBrief:
indicators = {}
close = None
for ind in ["RSI_14", "MACD_hist", "EMA_50"]:
resp = requests.get(
f"{BASE_URL}/indicator",
headers={"X-API-Key": API_KEY},
params={"symbol": symbol, "indicator": ind, "timeframe": "D1"},
)
data = resp.json()["data"]
indicators[ind] = data["value"]
if close is None:
close = data["bid"] # current bid as the reference price
rsi = indicators["RSI_14"]
ema = indicators["EMA_50"]
trend = "bullish" if close > ema else "bearish"
brief = SymbolBrief(
symbol=symbol,
close=close,
rsi=rsi,
macd_signal="bullish" if indicators["MACD_hist"] > 0 else "bearish",
trend=trend,
)
# Flag notable conditions
if rsi > 75 or rsi < 25:
brief.notable = True
brief.note = f"RSI at {rsi:.1f} -- {'overbought' if rsi > 75 else 'oversold'}"
return brief
briefs = [fetch_symbol_brief(sym) for sym in WATCHLIST] Step 2: Generate the Market Summary
from datetime import datetime
def generate_summary(briefs: list[SymbolBrief]) -> str:
"""Generate a plain-text market summary."""
date = datetime.utcnow().strftime("%B %d, %Y")
lines = [f"Market Brief -- {date}", "=" * 40, ""]
# Notable signals first
notable = [b for b in briefs if b.notable]
if notable:
lines.append("NOTABLE SIGNALS:")
for b in notable:
lines.append(f" {b.symbol}: {b.note}")
lines.append("")
# Full watchlist
lines.append("WATCHLIST OVERVIEW:")
for b in briefs:
arrow = "^" if b.trend == "bullish" else "v"
lines.append(
f" {b.symbol}: {b.close:.5f} {arrow} | "
f"RSI: {b.rsi:.1f} | MACD: {b.macd_signal} | "
f"Trend: {b.trend}"
)
return "\n".join(lines) Step 3: Format as HTML Email
def format_html_email(briefs: list[SymbolBrief]) -> str:
date = datetime.utcnow().strftime("%B %d, %Y")
rows = ""
for b in briefs:
color = "#22c55e" if b.trend == "bullish" else "#ef4444"
highlight = ' style="background:#fef3c7;"' if b.notable else ""
rows += f"""
<tr{highlight}>
<td style="padding:8px;font-weight:bold;">{b.symbol}</td>
<td style="padding:8px;">{b.close:.5f}</td>
<td style="padding:8px;">{b.rsi:.1f}</td>
<td style="padding:8px;color:{color};">{b.trend}</td>
<td style="padding:8px;">{b.note or '-'}</td>
</tr>"""
return f"""
<div style="font-family:sans-serif;max-width:600px;margin:0 auto;">
<h1 style="color:#1a1a2e;">Market Brief</h1>
<p style="color:#666;">{date}</p>
<table style="width:100%;border-collapse:collapse;border:1px solid #ddd;">
<tr style="background:#f8f9fa;">
<th style="padding:8px;text-align:left;">Symbol</th>
<th style="padding:8px;">Price</th>
<th style="padding:8px;">RSI</th>
<th style="padding:8px;">Trend</th>
<th style="padding:8px;">Note</th>
</tr>
{rows}
</table>
<p style="color:#999;font-size:12px;margin-top:20px;">
Data sourced from TickAtlas API. Not financial advice.
</p>
</div>""" Step 4: Send via Email Service
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
def send_newsletter(html_content: str, recipients: list[str]):
msg = MIMEMultipart("alternative")
msg["Subject"] = f"Market Brief - {datetime.utcnow().strftime('%b %d')}"
msg["From"] = "[email protected]"
msg.attach(MIMEText(html_content, "html"))
with smtplib.SMTP("smtp.sendgrid.net", 587) as server:
server.starttls()
server.login("apikey", "YOUR_SENDGRID_KEY")
for recipient in recipients:
msg["To"] = recipient
server.sendmail(msg["From"], recipient, msg.as_string())
# Full pipeline
briefs = [fetch_symbol_brief(sym) for sym in WATCHLIST]
html = format_html_email(briefs)
send_newsletter(html, ["[email protected]"]) Scaling Ideas
Add AI commentary
Feed the market data to an LLM and generate a paragraph of analysis for each notable signal. See our prompt engineering guide.
Personalized watchlists
Let subscribers choose which symbols they want in their brief. Store preferences in a database and generate per-user content.
Alert-based extras
Send intraday alerts when RSI hits extreme levels. Use the same API calls but trigger on threshold breaches instead of a schedule.
Run this against live data.
Every account starts pay-as-you-go with $2.50 of credit and no card. Paste the key into the samples above and the requests work unchanged.