I Built a Daily News Newsletter Bot with Hermes Agent β Here's Everything That Went Wrong (and Right)
Automating Daily Intelligence Briefings with AI Agents: A Production-Ready Architecture
Current Situation Analysis
Modern development teams and technical professionals face a persistent information fragmentation problem. Staying current across multiple domains (regional news, global events, industry-specific updates, and emerging technologies) traditionally requires subscribing to dozens of RSS feeds, paying for premium news APIs, or manually curating content daily. The operational overhead scales linearly with the number of topics, while the signal-to-noise ratio degrades rapidly.
AI agent frameworks promise to collapse this complexity into natural-language instructions. Instead of maintaining parsers, handling rate limits, and formatting raw JSON responses, developers can instruct an agent to search, synthesize, and structure content. However, the transition from prototype to reliable daily automation exposes a gap between agent capabilities and infrastructure realities. Most tutorials gloss over three critical failure points:
- Ephemeral environment state: Cloud development containers reset on rebuild, wiping installed dependencies, environment variables, and generated artifacts.
- Module resolution conflicts: Modern Node.js projects default to ES modules (
"type": "module"), which breaks legacy TypeScript execution tools that expect CommonJS. - Email deliverability mechanics: SMTP authentication, spam filtering heuristics, and dynamic header generation are rarely covered in AI-focused guides, yet they determine whether an automated pipeline actually reaches the inbox.
The technical facts remain consistent across implementations: zero-cost stacks are achievable using open-source agent runtimes (Hermes Agent), free-tier LLM routing (OpenRouter), and standard SMTP providers (Gmail). But achieving reliability requires treating the agent as one component in a broader delivery pipeline, not as a self-contained solution.
WOW Moment: Key Findings
When comparing traditional content aggregation against AI-agent-driven generation, the operational trade-offs shift dramatically. The following comparison illustrates why agent-based pipelines are gaining traction despite higher initial configuration friction.
| Approach | Monthly Cost | Maintenance Hours/Month | Content Adaptability | Infrastructure Complexity |
|---|---|---|---|---|
| RSS/API Aggregator | $0β$50 (API tiers) | 4β8 (parser breaks, format changes) | Low (static templates) | Medium (cron, error handling, fallbacks) |
| AI Agent Pipeline | $0 (free-tier routing) | 1β2 (prompt tuning, env management) | High (natural language instructions) | High (state persistence, deliverability, module config) |
Why this matters: The AI agent pipeline trades upfront configuration complexity for long-term adaptability. Instead of rewriting parsers when a news site changes its HTML structure or API schema, you update a single prompt. The agent handles search, summarization, formatting, and file output. This enables rapid topic expansion (adding health tips, motivational quotes, or regional filters) without touching the delivery engine. The bottleneck shifts from content extraction to environment reliability and email deliverability.
Core Solution
Building a reliable daily briefing pipeline requires separating concerns: agent execution, content generation, email delivery, and automation scheduling. Each layer must be isolated, testable, and resilient to environment resets.
1. Environment & State Persistence
Cloud development environments (GitHub Codespaces, Gitpod, etc.) are ideal for prototyping but destructive by default. Rebuilding a container wipes node_modules, pip packages, .env files, and shell exports. The solution is declarative environment configuration.
Create .devcontainer/devcontainer.json to automate dependency installation and environment restoration:
{
"name": "intelligence-briefing-pipeline",
"image": "mcr.microsoft.com/devcontainers/typescript-node:20",
"postCreateCommand": "npm install && pip install hermes-agent",
"customizations": {
"vscode": {
"extensions": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
}
}
}
Store secrets outside the working directory to survive container rebuilds. Hermes reads from ~/.hermes/.env by default. Keep project-level configuration in a version-controlled template, and inject runtime secrets via the persistent home directory.
2. Agent Configuration & Routing
Hermes Agent requires two explicit configurations to function: an inference provider key and a target model identifier. Omitting either results in cryptic runtime errors.
# ~/.hermes/.env
AGENT_ROUTER_KEY=sk-or-xxxxxxxxxxxxxxxx
HERMES_MODEL=owlobot/owl-7b
SMTP_HOST=smtp.gmail.com
SMTP_PORT=465
SMTP_USER=your-email@gmail.com
SMTP_PASS=your-16-char-app-password
RECIPIENT_LIST=team@company.com,lead@company.com
Architecture decision: Use OpenRouter for LLM routing. It abstracts provider-specific API differences, supports free-tier models, and allows seamless model swapping without code changes. Hermes acts as the execution layer, handling web search, content synthesis, and file I/O.
3. Content Generation Pipeline
The agent executes natural-language instructions to fetch, summarize, and format content. Instead of embedding prompts in code, store them as reusable skill files. This enables version control and prompt iteration without redeploying.
Create skills/daily_intel.md:
Today is {{TODAY}}. Search the web for the top 5 headlines in:
- Canadian regional news
- Global geopolitical updates
- Indian technology & business
- AI/ML research & industry shifts
Append one evidence-based health tip and one verified motivational quote.
Format the output as clean Markdown. Save to {{OUTPUT_PATH}}.
Execute via CLI or cron:
hermes chat "Execute skill at skills/daily_intel.md using today's date. Save output to briefings/$(date +%Y-%m-%d).md"
4. Delivery Engine (TypeScript)
Separate content generation from email delivery. This allows independent testing, retry logic, and failure isolation. Use tsx instead of ts-node to avoid ESM/CommonJS conflicts in modern Node.js projects.
Create dispatch-briefing.ts:
import nodemailer from "nodemailer";
import fs from "fs/promises";
import path from "path";
interface MailConfig {
host: string;
port: number;
secure: boolean;
user: string;
pass: string;
}
interface DispatchParams {
filePath: string;
recipients: string[];
config: MailConfig;
}
async function verifyTransport(config: MailConfig) {
const transporter = nodemailer.createTransport(config);
await transporter.verify();
return transporter;
}
async function dispatchBriefing({ filePath, recipients, config }: DispatchParams) {
const content = await fs.readFile(filePath, "utf-8");
const dateStr = new Date().toLocaleDateString("en-CA", {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
});
const transporter = await verifyTransport(config);
await transporter.sendMail({
from: `"Intelligence Brief π‘" <${config.user}>`,
to: recipients.join(", "),
subject: `Daily Intel β ${dateStr}`,
text: content,
headers: {
"X-Priority": "3",
"X-Mailer": "NodeJS-Briefing-Pipeline",
},
});
console.log(`[DISPATCH] Briefing delivered to ${recipients.length} recipient(s)`);
}
const args = process.argv.slice(2);
if (args.length < 1) {
console.error("Usage: npx tsx dispatch-briefing.ts <path-to-md>");
process.exit(1);
}
const mailConfig: MailConfig = {
host: process.env.SMTP_HOST || "smtp.gmail.com",
port: Number(process.env.SMTP_PORT) || 465,
secure: true,
user: process.env.SMTP_USER || "",
pass: process.env.SMTP_PASS || "",
};
dispatchBriefing({
filePath: path.resolve(args[0]),
recipients: (process.env.RECIPIENT_LIST || "").split(",").filter(Boolean),
config: mailConfig,
}).catch((err) => {
console.error("[DISPATCH FAILURE]", err.message);
process.exit(1);
});
Architecture decision: Explicit transport verification prevents silent hangs. Dynamic subject lines and custom headers reduce spam filtering. Separating configuration from execution enables unit testing and dry-run modes.
5. Automation Scheduling
Hermes includes a built-in cron scheduler. Combine generation and delivery in a single scheduled task:
hermes cron start
/cron add "0 8 * * *" "Execute skill at skills/daily_intel.md. Save to briefings/\$(date +\%Y-\%m-\%d).md. Then run: npx tsx dispatch-briefing.ts briefings/\$(date +\%Y-\%m-\%d).md"
Note: Cloud development environments suspend on inactivity. For production always-on execution, migrate the pipeline to a lightweight VPS or containerized runtime with persistent storage.
Pitfall Guide
| Pitfall | Explanation | Fix |
|---|---|---|
| Silent SMTP Hangs | Typos in hostname (smpt vs smtp) or mismatched port/secure flags cause Nodemailer to hang without throwing errors. |
Validate environment variables before transport creation. Use transporter.verify() to catch auth/network issues early. |
| Ephemeral State Loss | Rebuilding cloud containers wipes node_modules, pip packages, .env files, and shell exports. |
Use .devcontainer/devcontainer.json with postCreateCommand. Store secrets in persistent home directories (~/.hermes/.env). |
| ESM Module Conflicts | "type": "module" in package.json breaks ts-node, which expects CommonJS resolution. |
Replace ts-node with tsx. It handles both ESM and CommonJS natively without configuration overrides. |
| Spam Folder Delivery | Static subjects, missing sender names, and unverified app passwords trigger Gmail's spam heuristics. | Use dynamic date-based subjects, formatted sender strings, and Gmail App Passwords. Mark first delivery as "Not Spam" to train filters. |
| Incomplete Agent Config | Hermes requires both an API key and a model identifier. Missing either causes HTTP 400 or provider errors. |
Validate HERMES_MODEL and routing key exist before execution. Add pre-flight checks in your automation script. |
| File Race Conditions | The delivery script may read the markdown file before the agent finishes writing it. | Implement a file-wait utility or use fs.stat() polling. Alternatively, chain commands with && and verify file size > 0 before dispatch. |
| Idle Environment Suspension | Codespaces and similar environments auto-suspend after inactivity, breaking cron schedules. | Migrate to a always-on VPS, use systemd timers, or deploy to a lightweight container runtime with wake-on-cron support. |
Production Bundle
Action Checklist
- Initialize
.devcontainer/devcontainer.jsonwith explicit dependency installation commands - Store all secrets in persistent home directories, never in shell exports or working directory
.envfiles - Replace
ts-nodewithtsxto resolve ESM/CommonJS conflicts in modern Node.js projects - Implement transport verification (
transporter.verify()) before attempting email dispatch - Use dynamic subject lines and formatted sender strings to bypass spam filters
- Validate both API key and model identifier exist before triggering agent execution
- Add file existence/size checks to prevent race conditions between generation and delivery
- Migrate from ephemeral cloud environments to a persistent VPS or container runtime for production scheduling
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|---|---|---|
| Prototyping & Learning | GitHub Codespaces + Hermes cron | Zero setup cost, instant environment reset, ideal for iteration | $0 (free tier) |
| Small Team Briefings | Lightweight VPS (DigitalOcean/Linode) + systemd timer | Always-on execution, persistent storage, predictable performance | $5β$10/month |
| Enterprise Distribution | Containerized pipeline (Docker) + SMTP relay (SendGrid/Mailgun) | Scalable delivery, analytics, compliance, retry logic | $0β$50/month (depends on volume) |
| High-Frequency Updates | Serverless function (AWS Lambda/Cloudflare Workers) + queue | Event-driven, auto-scaling, pay-per-execution | $0β$5/month (low volume) |
Configuration Template
.devcontainer/devcontainer.json
{
"name": "briefing-pipeline",
"image": "mcr.microsoft.com/devcontainers/typescript-node:20",
"postCreateCommand": "npm install && pip install hermes-agent",
"forwardPorts": [],
"customizations": {
"vscode": {
"extensions": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
}
}
}
package.json (relevant fields)
{
"name": "intelligence-briefing",
"type": "module",
"scripts": {
"dispatch": "tsx dispatch-briefing.ts",
"generate": "hermes chat"
},
"devDependencies": {
"tsx": "^4.7.0",
"typescript": "^5.4.0"
},
"dependencies": {
"nodemailer": "^6.9.0"
}
}
~/.hermes/.env (template)
AGENT_ROUTER_KEY=sk-or-xxxxxxxxxxxxxxxx
HERMES_MODEL=owlobot/owl-7b
SMTP_HOST=smtp.gmail.com
SMTP_PORT=465
SMTP_USER=your-email@gmail.com
SMTP_PASS=your-16-char-app-password
RECIPIENT_LIST=team@company.com
Quick Start Guide
- Initialize environment: Create
.devcontainer/devcontainer.jsonand open in your preferred cloud or local dev container runtime. Dependencies install automatically. - Configure agent: Populate
~/.hermes/.envwith your OpenRouter key, target model, and SMTP credentials. Verify withhermes model. - Test generation: Run
hermes chat "Execute skill at skills/daily_intel.md. Save to briefings/test.md"and verify markdown output. - Test delivery: Execute
npx tsx dispatch-briefing.ts briefings/test.md. Check inbox and spam folder. Mark as "Not Spam" if needed. - Schedule automation: Run
hermes cron startand add the daily cron expression. Monitor first execution, then migrate to a persistent host for production use.
This architecture transforms fragmented information consumption into a reliable, self-maintaining pipeline. By isolating agent execution from delivery mechanics, enforcing persistent state management, and addressing email deliverability upfront, you eliminate the most common failure modes while preserving the flexibility of natural-language content generation.
Mid-Year Sale β Unlock Full Article
Base plan from just $4.99/mo or $49/yr
Sign in to read the full article and unlock all tutorials.
Sign In / Register β Start Free Trial7-day free trial Β· Cancel anytime Β· 30-day money-back
