How to Build a Newsletter Automation Agent with Claude Code
Learn how to build a newsletter automation agent using Claude Code, Perplexity, Nano Banana, and Gmail — from one prompt to a branded HTML email.
Why Newsletter Creation Is Still a Manual Mess
Most newsletter workflows are a collection of browser tabs and copy-paste cycles. Someone researches a topic, writes a draft in a Google Doc, pastes it into an email builder, tweaks the formatting for an hour, and finally hits send. Every week. The same sequence.
The tools exist to automate this. What’s been missing is a clean way to chain them together into something that actually runs end-to-end from a single instruction.
That’s what this guide covers. You’ll build a newsletter automation agent using Claude Code as the orchestrator, Perplexity for real-time web research, Nano Banana for rendering branded HTML emails, and Gmail for delivery. The end result: one prompt turns into a polished, sent newsletter — no manual steps in between.
This isn’t a vague overview. You’ll get the actual setup, the agent logic, and enough code to build and run this yourself.
What the Agent Actually Does
Before getting into implementation, it helps to see the full picture.
The agent takes a single input — a topic or brief like “AI tools for small businesses this week” — and executes the following pipeline:
- Research — Queries Perplexity’s API to pull fresh, sourced information on the topic
- Synthesize — Processes the research and structures it into newsletter-ready content (intro, sections, key takeaways)
- Format — Sends the structured content to Nano Banana, which renders a responsive, on-brand HTML email
- Deliver — Authenticates with the Gmail API and sends the finished email to a recipient list
Claude Code handles the orchestration. It writes the scripts, runs them, debugs issues, and iterates until the pipeline works. You’re not writing Python from scratch — you’re directing an agent that writes and executes the code for you.
This distinction matters. The goal isn’t just to automate a newsletter; it’s to build an agent that can handle the entire process autonomously once you’ve set it up.
The Tech Stack, Explained
Understanding each tool’s role makes the integration logic much clearer.
Claude Code
Claude Code is Anthropic’s CLI-based coding agent. You run it in your terminal, give it natural language instructions, and it reads files, writes code, executes commands, calls APIs, and iterates based on results.
For this project, Claude Code acts as the brain and executor. It doesn’t just write code for you to run — it runs the code, sees what happens, fixes errors, and keeps going until the task is done.
Claude Code has tool access built in: it can read and write files, run shell commands, search the web, and call external APIs. That makes it well suited for building multi-step automation workflows where the agent needs to adapt based on intermediate results.
Perplexity
Perplexity’s Sonar API provides access to real-time web search and synthesis. Unlike a static language model, Perplexity searches the live web and returns information with citations, which makes it reliable for pulling current events, product updates, or anything time-sensitive.
For a newsletter agent, Perplexity solves the freshness problem. Claude’s training data has a cutoff date; Perplexity doesn’t. You can ask it to find the latest news on a topic and get results from the past 24 hours.
The API is OpenAI-compatible, so integration is straightforward. You hit a /chat/completions endpoint with a model name like sonar-pro and get back a structured response with sources included.
Nano Banana
Nano Banana handles the visual rendering layer. It takes structured content — typically JSON or markdown — and converts it into a responsive, styled HTML email.
Building good HTML emails from scratch is genuinely painful. They need to work across dozens of email clients, many of which have limited CSS support. Nano Banana abstracts that complexity. You send content, define branding (colors, fonts, logo URL), and get back a fully rendered HTML string you can drop into any email API.
For this workflow, it’s the step between “content as text” and “content as a polished email people actually want to open.”
Gmail API
Gmail’s API handles the final send. With OAuth2 authentication, you can send emails programmatically from any Gmail or Google Workspace account. The API supports sending raw MIME messages, which means you can pass in the HTML output from Nano Banana directly.
For a newsletter, you might send to a single test address first, then expand to a list stored in a CSV or Google Sheet.
Prerequisites and Environment Setup
Here’s what you need before starting.
Accounts and API Access
- Claude Code — Installed via
npm install -g @anthropic-ai/claude-code. Requires an Anthropic account and API key. - Perplexity API — Create an account at perplexity.ai, navigate to API settings, and generate a key. Sonar Pro access requires a paid plan.
- Nano Banana — Sign up and generate an API key from the dashboard. Note your workspace ID — you’ll need it in the API requests.
- Google Cloud — Create a project in the Google Cloud Console. Enable the Gmail API. Create OAuth2 credentials (Desktop App type). Download the
credentials.jsonfile.
Local Environment
You’ll need Python 3.9+ and pip. Create a project directory and set up a virtual environment:
mkdir newsletter-agent
cd newsletter-agent
python3 -m venv venv
source venv/bin/activate
Install the required libraries:
pip install openai google-auth google-auth-oauthlib google-api-python-client python-dotenv requests
Create a .env file in your project root:
PERPLEXITY_API_KEY=your_perplexity_key_here
NANO_BANANA_API_KEY=your_nano_banana_key_here
NANO_BANANA_WORKSPACE_ID=your_workspace_id_here
NEWSLETTER_RECIPIENT=your@email.com
Move your downloaded credentials.json into the project directory. Your project structure should look like this:
newsletter-agent/
├── .env
├── credentials.json
├── venv/
└── (scripts will be created here)
Starting Claude Code and Giving It the Project Brief
With the environment ready, start Claude Code in your project directory:
claude
Claude Code opens an interactive session in your terminal. It can see your files, run commands in the current directory, and call external services. You give it instructions; it acts on them.
Start with a clear, detailed project brief. Vague prompts produce vague results. Here’s a prompt that works well:
I want to build a newsletter automation agent. Here's the workflow:
1. Accept a topic string as input
2. Use the Perplexity API (key in .env) to research that topic and retrieve
recent, relevant information with citations
3. Process the Perplexity response and structure the content into a newsletter
format: a subject line, intro paragraph, 3-4 main sections with headers and
body text, and a closing takeaway
4. Send the structured content to the Nano Banana API (key and workspace ID
in .env) to render a branded HTML email — use dark blue (#1A2B4A) as the
primary color and include our company name "Weekly Signal" in the header
5. Send the rendered HTML via the Gmail API to the address in .env —
credentials are in credentials.json
Create modular Python scripts for each step and a main orchestration script
called run_newsletter.py that takes the topic as a command-line argument.
Claude Code will read your .env and directory structure, then start building.
Building the Research Layer
Claude Code will generate a research module. Here’s the kind of code it produces for the Perplexity integration — useful to understand what it’s doing under the hood.
The Perplexity Query
Perplexity’s Sonar models work through an OpenAI-compatible API. The key is to write a well-structured system prompt that tells Perplexity how to format its response.
Claude Code typically generates something like this:
# research.py
import os
from openai import OpenAI
from dotenv import load_dotenv
load_dotenv()
def research_topic(topic: str) -> dict:
"""
Query Perplexity's Sonar API to research a topic.
Returns structured content with citations.
"""
client = OpenAI(
api_key=os.getenv("PERPLEXITY_API_KEY"),
base_url="https://api.perplexity.ai"
)
system_prompt = """You are a research assistant for a professional newsletter.
When given a topic, search for the most recent and relevant information.
Structure your response as follows:
- A one-sentence overview of the topic
- 3-4 key developments or findings (each 2-3 sentences)
- A list of the most important takeaways
Be specific, cite facts, and focus on what's new or notable.
Include publication dates where known."""
response = client.chat.completions.create(
model="sonar-pro",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": f"Research this topic for a newsletter: {topic}"}
],
temperature=0.2,
max_tokens=2000
)
return {
"content": response.choices[0].message.content,
"citations": getattr(response, 'citations', []),
"topic": topic
}
The temperature=0.2 keeps the output factual and consistent. Higher values introduce more variation, which isn’t useful for research summaries.
Why Citations Matter
Perplexity includes source citations in its responses. Claude Code extracts these and stores them alongside the content. Later, you can choose whether to include them in the newsletter footer as “Sources” links — which adds credibility and keeps you transparent with readers.
Claude Code will test this module automatically before moving on. If the API call fails (wrong key, network issue, model not available), it will catch the error, print a useful message, and ask you how to proceed.
Generating Structured Newsletter Content
Raw research output isn’t newsletter-ready. The Perplexity response is accurate and sourced, but it needs to be reshaped into the format your email template expects.
Claude Code generates a content processing module that takes the research output and produces a clean data structure.
The Content Model
# content.py
import json
import anthropic
from dotenv import load_dotenv
load_dotenv()
def structure_newsletter_content(research: dict) -> dict:
"""
Take raw research output and structure it into newsletter format.
Uses Claude to rewrite and organize the content.
"""
client = anthropic.Anthropic()
prompt = f"""Take the following research and structure it into a professional
newsletter. The newsletter is called 'Weekly Signal' and covers technology and
business topics.
Research content:
{research['content']}
Topic: {research['topic']}
Return a JSON object with exactly this structure:
{{
"subject_line": "compelling email subject, under 50 characters",
"preview_text": "preview text shown in inbox, under 90 characters",
"intro": "2-3 sentence intro that hooks the reader",
"sections": [
{{
"heading": "section heading",
"body": "2-3 paragraphs of newsletter prose"
}}
],
"takeaway": "1-2 sentence closing insight or call to action",
"topic": "{research['topic']}"
}}
Write in a clear, professional but conversational tone. No buzzwords.
Return only valid JSON, no other text."""
response = client.messages.create(
model="claude-opus-4-5",
max_tokens=3000,
messages=[{"role": "user", "content": prompt}]
)
content_text = response.content[0].text.strip()
# Claude Code adds error handling here automatically
try:
newsletter_data = json.loads(content_text)
except json.JSONDecodeError:
# Extract JSON if wrapped in markdown code blocks
import re
json_match = re.search(r'\{.*\}', content_text, re.DOTALL)
if json_match:
newsletter_data = json.loads(json_match.group())
else:
raise ValueError("Could not parse structured content from Claude response")
newsletter_data['citations'] = research.get('citations', [])
return newsletter_data
Claude Code typically handles the JSON parsing edge case automatically — it anticipates that models sometimes wrap JSON in markdown fences, and adds regex extraction as a fallback.
What Good Structured Content Looks Like
After this step, you have a clean Python dictionary that looks something like:
{
"subject_line": "AI coding tools are changing dev workflows",
"preview_text": "Three tools reshaping how developers write and review code",
"intro": "Developers are spending less time on boilerplate and more on architecture. A new set of AI coding tools made meaningful progress this week.",
"sections": [
{
"heading": "Claude Code gets multi-file editing",
"body": "Anthropic updated Claude Code with..."
},
...
],
"takeaway": "The gap between AI-assisted and manual development is widening. Teams that haven't evaluated these tools this quarter should put it on the agenda.",
"topic": "AI tools for developers this week"
}
This structure maps directly to what Nano Banana’s API expects, which is why the content module matters — it’s the adapter between research output and email template input.
Rendering Branded HTML with Nano Banana
This is where the content becomes a real email. Nano Banana’s API accepts structured content and branding configuration, and returns a fully rendered HTML string optimized for email clients.
Calling the Nano Banana API
Claude Code generates the rendering module:
# render.py
import os
import requests
from dotenv import load_dotenv
load_dotenv()
def render_email_html(newsletter_data: dict) -> dict:
"""
Send structured newsletter content to Nano Banana API.
Returns rendered HTML and subject line.
"""
api_key = os.getenv("NANO_BANANA_API_KEY")
workspace_id = os.getenv("NANO_BANANA_WORKSPACE_ID")
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
# Build sections array for the API
sections = [
{
"type": "hero",
"content": newsletter_data["intro"]
}
]
for section in newsletter_data.get("sections", []):
sections.append({
"type": "content",
"heading": section["heading"],
"body": section["body"]
})
sections.append({
"type": "closing",
"content": newsletter_data["takeaway"]
})
# Add citations if present
if newsletter_data.get("citations"):
citation_text = "\n".join([f"• {c}" for c in newsletter_data["citations"][:5]])
sections.append({
"type": "footer_note",
"content": f"Sources:\n{citation_text}"
})
payload = {
"workspace_id": workspace_id,
"template": "newsletter_standard",
"branding": {
"primary_color": "#1A2B4A",
"accent_color": "#4A90E2",
"brand_name": "Weekly Signal",
"font_family": "Georgia, serif"
},
"content": {
"subject": newsletter_data["subject_line"],
"preview_text": newsletter_data["preview_text"],
"sections": sections
}
}
response = requests.post(
"https://api.nanobanana.io/v1/render",
headers=headers,
json=payload,
timeout=30
)
response.raise_for_status()
result = response.json()
return {
"html": result["html"],
"subject": newsletter_data["subject_line"],
"preview_text": newsletter_data["preview_text"]
}
What Nano Banana Handles for You
The rendered HTML that comes back is production-ready. Nano Banana handles:
- Responsive layouts — Email renders correctly on mobile and desktop
- Email client compatibility — Inline styles, table-based layouts where required, Outlook-safe markup
- Brand consistency — Your colors, fonts, and brand name applied consistently throughout
- Plain text fallback — Automatically generated for email clients that don’t render HTML
Without a tool like this, you’d be maintaining a Jinja2 template full of inline styles, testing across Gmail, Outlook, and Apple Mail, and debugging rendering issues on every update. Nano Banana removes that from the equation.
Sending via the Gmail API
The final step is delivery. Gmail’s API requires OAuth2 authentication, which involves a one-time browser-based login flow. After that, credentials are saved locally and the agent can send without further interaction.
First-Time Authentication
# gmail_auth.py
import os
import pickle
from google.auth.transport.requests import Request
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
SCOPES = ['https://www.googleapis.com/auth/gmail.send']
def get_gmail_service():
"""
Authenticate with Gmail API and return service object.
Handles token refresh automatically after first-time auth.
"""
creds = None
if os.path.exists('token.pickle'):
with open('token.pickle', 'rb') as token:
creds = pickle.load(token)
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES
)
creds = flow.run_local_server(port=0)
with open('token.pickle', 'wb') as token:
pickle.dump(creds, token)
return build('gmail', 'v1', credentials=creds)
The first time you run this, it opens a browser window for you to authorize the application. After that, the token is saved locally and silently refreshed.
Sending the Email
# send.py
import os
import base64
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from gmail_auth import get_gmail_service
from dotenv import load_dotenv
load_dotenv()
def send_newsletter(rendered: dict, recipient: str = None) -> dict:
"""
Send the rendered HTML newsletter via Gmail API.
"""
service = get_gmail_service()
if not recipient:
recipient = os.getenv("NEWSLETTER_RECIPIENT")
msg = MIMEMultipart("alternative")
msg["Subject"] = rendered["subject"]
msg["To"] = recipient
msg["From"] = "me" # Gmail API resolves "me" to authenticated account
# Plain text fallback
plain_text = f"{rendered['subject']}\n\n{rendered.get('preview_text', '')}"
msg.attach(MIMEText(plain_text, "plain"))
# HTML version
msg.attach(MIMEText(rendered["html"], "html"))
raw_message = base64.urlsafe_b64encode(
msg.as_bytes()
).decode("utf-8")
result = service.users().messages().send(
userId="me",
body={"raw": raw_message}
).execute()
return {
"message_id": result["id"],
"recipient": recipient,
"subject": rendered["subject"]
}
Claude Code tests this step by sending to your own address first. It confirms the send succeeded before marking the task complete.
The Orchestration Script
Claude Code ties everything together in run_newsletter.py:
# run_newsletter.py
import sys
import time
from research import research_topic
from content import structure_newsletter_content
from render import render_email_html
from send import send_newsletter
def run_pipeline(topic: str, recipient: str = None):
print(f"\n📰 Starting newsletter pipeline for: '{topic}'\n")
# Step 1: Research
print("🔍 Researching topic with Perplexity...")
start = time.time()
research = research_topic(topic)
print(f" Done in {time.time() - start:.1f}s")
# Step 2: Structure content
print("✍️ Structuring newsletter content...")
start = time.time()
structured = structure_newsletter_content(research)
print(f" Subject: {structured['subject_line']}")
print(f" Done in {time.time() - start:.1f}s")
# Step 3: Render HTML
print("🎨 Rendering HTML email...")
start = time.time()
rendered = render_email_html(structured)
print(f" Done in {time.time() - start:.1f}s")
# Step 4: Send
print("📤 Sending via Gmail...")
result = send_newsletter(rendered, recipient)
print(f"\n✅ Newsletter sent!")
print(f" To: {result['recipient']}")
print(f" Subject: {result['subject']}")
print(f" Message ID: {result['message_id']}\n")
return result
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python run_newsletter.py 'your topic here'")
sys.exit(1)
topic = sys.argv[1]
recipient = sys.argv[2] if len(sys.argv) > 2 else None
run_pipeline(topic, recipient)
Running it looks like this:
python run_newsletter.py "AI tools for developers this week"
The pipeline runs end to end in roughly 30–60 seconds, depending on API response times. You get a sent email with a branded layout, sourced content, and a coherent structure — from a single command.
Scheduling and Automation
Running the script manually is useful for testing. For a real newsletter operation, you want it to run on a schedule without any intervention.
Using Cron (Linux/macOS)
Open your crontab with crontab -e and add:
# Run every Monday at 8am
0 8 * * 1 /path/to/venv/bin/python /path/to/newsletter-agent/run_newsletter.py "weekly AI news" >> /path/to/newsletter.log 2>&1
Make sure the cron job uses the Python from your virtual environment, not the system Python, or the installed packages won’t be available.
Using GitHub Actions
For a cloud-based schedule with zero local dependencies:
# .github/workflows/newsletter.yml
name: Weekly Newsletter
on:
schedule:
- cron: '0 8 * * 1' # Every Monday at 8am UTC
workflow_dispatch: # Also allows manual trigger from GitHub UI
jobs:
send-newsletter:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: pip install -r requirements.txt
- name: Run newsletter pipeline
env:
PERPLEXITY_API_KEY: ${{ secrets.PERPLEXITY_API_KEY }}
NANO_BANANA_API_KEY: ${{ secrets.NANO_BANANA_API_KEY }}
NANO_BANANA_WORKSPACE_ID: ${{ secrets.NANO_BANANA_WORKSPACE_ID }}
NEWSLETTER_RECIPIENT: ${{ secrets.NEWSLETTER_RECIPIENT }}
GMAIL_TOKEN: ${{ secrets.GMAIL_TOKEN }}
run: python run_newsletter.py "AI and tech updates this week"
For GitHub Actions, you’ll need to serialize the Gmail OAuth token and store it as a secret, then deserialize it at runtime. Claude Code can handle this — ask it to “add GitHub Actions support with serialized Gmail auth.”
Troubleshooting Common Issues
Claude Code catches most errors automatically, but here are issues that sometimes need manual intervention.
Perplexity Returns Empty Citations
The citations field is only populated on certain Sonar models. If you’re using sonar-small, switch to sonar-pro or sonar-large. Check the Perplexity API docs for which models include web citations in their response.
Gmail Auth Loop
If token.pickle exists but authentication keeps failing, delete the file and re-authenticate:
rm token.pickle
python run_newsletter.py "test topic"
A browser window will open for the OAuth flow. Complete it, and the new token will be saved.
Nano Banana Returns 422 Unprocessable Entity
This usually means the content structure doesn’t match the API’s schema. Check that:
sectionsis a non-empty array- Each section has the required
typefield workspace_idmatches what’s in your dashboard (not the API key)
Claude Returns Malformed JSON
The content module handles this with regex extraction, but sometimes the JSON is genuinely malformed (unclosed brackets, etc.). If this happens repeatedly, add a stricter system prompt: "Return ONLY a valid JSON object. No markdown, no code blocks, no additional text."
The Email Lands in Spam
This is a deliverability issue, not a code issue. To improve inbox placement:
- Send from a Gmail account with good reputation (not brand new)
- Keep the HTML-to-text ratio reasonable (Nano Banana handles this)
- Avoid spam trigger words in subject lines
- For newsletters sent to real lists, consider a dedicated sending domain with SPF/DKIM records configured
Taking This Further with MindStudio
What you’ve built is a custom agent that runs locally. It works well for personal use or small team workflows. But if you want to scale this — multiple newsletter topics, multiple recipient lists, different templates per audience segment, a scheduling UI that non-technical team members can use — you’re looking at infrastructure work that falls outside Claude Code’s scope.
That’s where MindStudio becomes useful. MindStudio is a no-code platform for building and deploying AI agents, and it includes native integrations with Gmail, Google Workspace, and external APIs like Perplexity.
You can rebuild this same newsletter pipeline as a MindStudio workflow — visually, without writing code — and then expose it as a web app with a simple input form. A team member fills in the topic, picks a recipient list, and clicks Run. MindStudio handles the API calls, the rendering, and the send, then logs the result.
The Agent Skills Plugin (@mindstudio-ai/agent) also lets Claude Code or any other AI agent call MindStudio capabilities directly as typed method calls. If you want to keep Claude Code as your orchestrator but offload Gmail sending to MindStudio’s managed infrastructure, you can do that too: agent.sendEmail() handles auth, retries, and rate limiting without you managing any of it.
For teams that want the newsletter agent to run reliably on a schedule, with logging and an audit trail, without anyone manually managing cron jobs or GitHub Actions secrets, MindStudio is the cleaner path. You can try it free at mindstudio.ai.
Frequently Asked Questions
What is Claude Code and how does it differ from the Claude web interface?
Claude Code is a terminal-based coding agent. Unlike the Claude web interface, which is conversational, Claude Code runs locally in your development environment and has direct access to your filesystem, can execute shell commands, call APIs, and run code autonomously. It’s designed for software development tasks where the agent needs to read files, write code, test it, and iterate — not just generate text.
Does this newsletter agent work with Google Workspace accounts?
Yes. The Gmail API works with both personal Gmail accounts and Google Workspace (formerly G Suite) accounts. For Workspace accounts, your administrator may need to enable the Gmail API and grant the necessary OAuth scopes in the Admin Console. The authentication flow is otherwise identical.
How much does this setup cost to run?
It depends on volume. The main cost drivers are:
- Perplexity Sonar Pro — Roughly $5 per 1,000 requests at current pricing. One newsletter run is typically 1–2 requests.
- Claude API — The content structuring step uses Claude. At Claude Opus rates, one newsletter is under $0.10.
- Nano Banana — Pricing varies by plan; check their current rate card.
- Gmail API — Free within normal sending limits (500 emails/day for personal Gmail, higher for Workspace).
For a weekly newsletter, total cost is well under $1 per send.
Can I send to a list of subscribers instead of a single email address?
Yes. The current setup sends to one address. To send to a list, you can store addresses in a CSV or Google Sheet and loop through them in run_newsletter.py. For large lists (100+ subscribers), use Gmail’s batch send or switch to a transactional email service like SendGrid or Postmark, which Claude Code can integrate with in place of the Gmail module.
How do I customize the newsletter template?
Nano Banana’s branding object accepts colors, fonts, and logos. For structural changes — different section types, different layouts — check Nano Banana’s template library in their dashboard. You can also specify a custom template ID in the API request if you’ve built one in their editor.
Is Claude Code safe to use with API keys stored in .env files?
The .env file approach is standard for local development. Claude Code reads environment variables but does not transmit them to third parties beyond the APIs you explicitly call. Standard precautions apply: don’t commit .env to version control (add it to .gitignore), don’t share the file, and rotate keys if you suspect exposure. For production deployments, use a secrets manager rather than .env files.
Key Takeaways
Building a newsletter automation agent with Claude Code is a practical, achievable project that produces real value — not just a demo.
Here’s what to take away:
- Claude Code is an orchestrator, not just a code generator. It writes, executes, debugs, and iterates autonomously, which makes it well suited for multi-step automation workflows.
- Perplexity solves the freshness problem. Its Sonar API pulls live web data with citations, giving your newsletter content that reflects what’s actually happening — not what was true when a model was trained.
- Nano Banana removes the email rendering headache. Getting HTML emails to render consistently across clients is genuinely difficult. Delegating that to a purpose-built tool is the right call.
- Modular design matters. Keeping research, content structuring, rendering, and sending in separate modules means you can swap out any component without rebuilding the whole pipeline.
- The one-prompt-to-sent-email workflow is real. Once set up, the entire pipeline runs in under a minute from a single command. That’s the point of building it this way.
If you want to take this from a local script to a team-facing tool — with scheduling, a UI, and managed infrastructure — MindStudio is worth exploring. The same logic applies; the platform just handles the operational layer so you don’t have to.