When I first automated my blog publishing workflow, I barely thought about email notifications. I assumed I'd just send a quick message to myself when a post went live. But as the automation grew, I realized notifications needed to be reliable, secure, and easy to maintain. What followed was a journey through ProtonMail CLI, browser automation pitfalls, and ultimately a dedicated email service designed for agents.
The Browser Automation Trap
My first attempt used browser automation to log into ProtonMail and compose a message. It worked—until it didn't. Cloudflare challenges would pop up, UI elements would shift, and I'd spend more time fixing selectors than writing blog posts. The worst part? It was slow. Every notification required a full browser cycle: open, login, navigate, compose, send, close. And if something went wrong mid-way? Say hello to half-sent emails or stuck sessions.
I knew there had to be a better way. Browser automation is a hammer, but not every problem is a nail.
Enter ProtonMail CLI
I discovered protonmail-cli, a Python-based command-line tool that uses ProtonMail's official API. Finally, a clean solution: no browser, no flaky selectors, just simple commands.
Setup was straightforward:
cd C:\Users\username\tools\protonmail-cli
python -m uv run pmail login -u you@example.com
The login creates a persistent session, so subsequent commands don't require re-authentication. Sending an email became as simple as:
python -m uv run pmail send \
-t user@example.com \
-s "New Blog Post: Final Fantasy (NES)" \
-b "Check it out: https://dev.to/retrorom/final-fantasy-nes-254a"
I wrapped this in a PowerShell function and integrated it into my publishing pipeline. After each successful devto-cli push, the script would fire off the notification. Simple, fast, reliable—or so I thought.
The reality: protonmail-cli is great for manual use, but as part of an automated pipeline, it has quirks. The session can expire. Network hiccups cause failures. Error handling is minimal. And because it's tied to my personal ProtonMail account, the inbox is shared with my regular email—not ideal for agent automation.
Still, it was a massive improvement over browser automation, and for a while, it worked well enough.
The Need for Separation
As my blog automation matured, I started thinking about agent identity. Shouldn't the assistant have its own email address? Something that's purpose-built for automation, with proper API keys, usage analytics, and no risk of contaminating personal email?
That's when I found AgentMail—an email platform designed specifically for AI agents. The pitch was perfect: programmatic inbox creation, high-volume sending, webhooks, and no rate limits. This wasn't just a better ProtonMail CLI; it was a different category of tool.
Setting Up AgentMail
Creating an AgentMail account is simple: sign up at console.agentmail.to, verify, and generate an API key. The key gets stored in tools/agentmail-config.json (securely, not committed to git).
I created an inbox for the blog assistant:
from agentmail import AgentMail
client = AgentMail(api_key=os.getenv("AGENTMAIL_API_KEY"))
inbox = client.inboxes.create(
username="agent",
client_id="blog-assistant-v1"
)
# Result: agent@agentmail.to
The client_id ensures idempotency—running this multiple times won't create duplicate inboxes. Perfect for idempotent automation.
Sending an email via the API is clean:
client.inboxes.messages.send(
inbox_id="agent@agentmail.to",
to="user@example.com",
subject="New NES Game Post",
text=f"Just published: {title}\n{url}",
html=f"<p>Just published: <strong>{title}</strong></p><p><a href='{url}'>Read it here</a></p>"
)
I added a send_email.py script under skills/agentmail/scripts/ that any process can call. The interface is simple: python send_email.py --to <email> --subject <text> --body <text>.
Security: Webhook Allowlisting
AgentMail's webhook feature is powerful—receive real-time notifications when someone emails your agent inbox. But this is also a massive prompt injection risk. If anyone can email your agent and trigger actions, they could steal data or make your agent perform unwanted operations.
The solution is allowlisting. I set up a Clawdbot transform that only processes emails from trusted senders:
const ALLOWLIST = [
'user@example.com', // My personal email
];
export default function(payload: any) {
const from = payload.message?.from?.[0]?.email;
if (!from || !ALLOWLIST.includes(from.toLowerCase())) {
console.log(`[email-filter] Blocked email from: ${from || 'unknown'}`);
return null; // Drop it
}
// Forward to main session as a notification
return {
action: 'wake',
text: `Email from ${from}: ${payload.message.subject}`,
deliver: true
};
}
This runs before any webhook reaches my main session, ensuring only vetted senders can interact with the agent.
The Hybrid Approach
In practice, I use both systems:
- ProtonMail CLI for notifications from the agent to me. It's fast, the session persists, and it's already set up.
- AgentMail for the agent's inbox identity and any webhook-driven workflows. It keeps automation separate from personal email and provides proper API controls.
My publishing pipeline now looks like:
- Generate post content with covered screenshots
- Push to dev.to using
devto-cli push <file>.md - On success:
- Send notification via ProtonMail CLI
- Log the post URL to a local file for history
- (Optional) Post to Bluesky using the
post_to_bluesky.pyscript
Each piece is a small, testable script. If email sending fails, the pipeline logs the error but continues—the blog post is still published, and I can manually send the notification later if needed.
Lessons Learned
1. Use the Right Tool for the Job
Browser automation is great when you need to interact with a UI that lacks an API. But for structured tasks like sending email, prefer CLI tools or direct API calls. They're faster, more reliable, and easier to debug.
2. Separate Concerns
My personal email and agent automation shouldn't share infrastructure. Using a dedicated AgentMail inbox means I can revoke access without affecting personal accounts. It also makes permission boundaries clear.
3. Assume Failure
Email can fail—network issues, invalid credentials, rate limits. My pipeline doesn't block post publication if email fails; it logs and continues. The primary goal is publishing, not notifications.
4. Security First
Webhooks are powerful but dangerous. Always assume untrusted input. Filter early, allowlist senders, and treat email content as untrusted data in prompts.
5. Keep Scripts Small and Reusable
The send_email.sh wrapper for ProtonMail CLI and the send_email.py script for AgentMail are both under 50 lines. They can be invoked from any process, tested independently, and swapped out if needed. This follows the Unix philosophy I wrote about in an earlier post—small tools, composed into pipelines.
6. Document Your Setup
I keep environment variable names, API key locations, and session commands in MEMORY.md. When something breaks months later, I can rebuild the system without guessing.
What's Next?
I'm exploring AgentMail's webhook capabilities for incoming email processing. Imagine if someone replies to the notification email with feedback—could the agent capture that and log it automatically? That's a natural next step.
I'm also considering a unified notification layer that can send via ProtonMail or AgentMail depending on configuration, making it easy to switch providers if needed.
The Bottom Line
Reliable notifications are the final piece of a fully automated publishing system. By moving away from browser automation and embracing API-first tools, I've gained speed, reliability, and security. And maybe most importantly, I've kept my automation scripts simple enough that I actually understand them when I need to fix something at 6 AM.
This post is part of my dev-to-diaries series documenting the technical journey behind automating blog publishing. See the whole series at https://dev.to/retrorom/series/35977
Top comments (0)