Skip to main content

Your Email Failed. Can You Tell Me Why?

· 6 min read
Co-founder, mailbot

Your agent sent a welcome email to a new user. The user says they never got it. Now what?

You open your email provider's dashboard. You see "sent" next to the message. That's it. Sent. No delivery confirmation. No bounce reason. No timestamp for when the receiving server accepted or rejected it. Just "sent."

So you start digging. Server logs. Queue dashboards. Maybe you grep through raw SMTP output looking for a 550 code or a timeout. Thirty minutes later, you find out the email bounced because the recipient's mailbox was full. The information was there the whole time. It was just buried under layers of infrastructure you were never meant to read.

The Visibility Problem

Email is one of the few communication channels where "I sent it" and "they received it" are two completely different statements. Between those two moments, a message passes through DNS resolution, TLS negotiation, SPF/DKIM/DMARC validation, content filtering, reputation scoring, and inbox placement. Any one of those steps can fail silently.

Most email APIs treat this entire chain as a black box. You get a 200 OK when you submit the message, and you get nothing else unless you go looking for it. If the message bounces three hours later, you'll find out when you check a log file. If it never gets opened, you'll never know whether it was a deliverability problem or a subject line problem.

For developers building automated workflows, this is unacceptable. When your system sends hundreds or thousands of emails, you need to know exactly what happened to each one. Not eventually. Not by polling a status endpoint. In real time, with enough context to act on.

What mailbot Tracks

Every email sent or received through mailbot generates a series of structured events. These events form a timeline for each thread, giving you a complete narrative of what happened and when.

The event types map to real email lifecycle stages:

message.inbound fires when mailbot receives an email at one of your inboxes. The event includes the parsed sender, subject, thread assignment, and a reference to the full message content.

message.outbound fires when your code sends a message through the API. This confirms the message was accepted into the delivery pipeline.

message.delivered fires when the receiving mail server accepts the message. This is the confirmation that matters. Not "sent." Delivered.

message.bounced fires when delivery fails. The event payload includes the bounce type (hard or soft), the SMTP response code, and the reason string from the receiving server. You know exactly why it failed without reading a single log line.

message.opened and message.clicked fire when the recipient interacts with the email. These are engagement signals your code can act on immediately.

Every event is stored, timestamped, and associated with the specific thread and message that generated it. You're not querying a separate analytics system. The events live alongside the messages they describe.

The Timeline View

When you open a thread in mailbot's console, you see a vertical timeline of every event in chronological order. Each event card shows the type, status, and timestamp. Click to expand, and you see the full structured payload.

This is the difference between an email platform that was built for observability and one that bolted on a status page as an afterthought. The timeline doesn't just tell you what happened. It tells you the story of what happened, in order, with context.

You can filter the timeline to show only delivery events, only errors, or everything. When you're debugging a specific failure, the error filter strips away the noise and shows you exactly where the chain broke.

Event Payloads, Not Log Files

Every event in mailbot carries a structured JSON payload. When a notification fires to your registered endpoint, the payload includes all the context you need to make a decision in your code.

import { MailBot } from '@yopiesuryadi/mailbot-sdk';
const mailbot = new MailBot({ apiKey: 'mb_your_api_key' });

// List all events for a specific thread
const events = await mailbot.events.list('thread_abc123');

for (const event of events.data) {
console.log(`${event.type} | ${event.status} | ${event.created_at}`);

if (event.type === 'message.bounced') {
console.log('Bounce metadata:', event.metadata);
// { bounce_type: "hard", code: "550", reason: "mailbox not found" }
}
}

You can also retrieve a single event by ID to inspect its full payload:

from mailbot import MailBot
mailbot = MailBot(api_key="mb_your_api_key")

event = mailbot.events.get("evt_xyz789")
print(event.data.type) # "notification.failed"
print(event.data.status) # "failed"
print(event.data.metadata) # Full request/response details

The payload for notification events includes everything: the URL that was called, the HTTP status code returned, the response time, and the full request body. When something fails, you know why. When it succeeds, you have proof.

Replay: The Feature That Changes Everything

Here's where it gets interesting. Every notification event in mailbot can be replayed with a single API call.

// Replay a failed notification
const result = await mailbot.events.replay('evt_xyz789');

console.log(result.replayed); // true
console.log(result.status_code); // 200
console.log(result.duration_ms); // 142

Your endpoint was down for five minutes. Three notifications failed during that window. Instead of building retry infrastructure, instead of writing a script to re-fetch and re-process those events, you replay them. Each replay fires the original payload to your endpoint (or a different one if you specify a target URL). You get back the HTTP status code and response time so you can confirm it worked.

In the dashboard, this is a single button. Click "Replay" on any failed notification event. mailbot re-delivers the payload and shows you the result inline. No terminal. No curl commands. No guessing.

This matters because email event processing is inherently asynchronous. Failures happen. Endpoints go down. Deploys break things temporarily. The question isn't whether your notification processing will fail. The question is how fast you can recover when it does.

Why This Matters for Automated Systems

If you're building an AI agent that processes email, event visibility isn't a nice-to-have. It's the difference between a system you trust and a system you hope is working.

Consider a support agent that auto-replies to customer emails. Without event visibility, you know the agent sent a reply. You don't know if the reply was delivered. You don't know if the customer opened it. You don't know if the original notification that triggered the agent was processed correctly.

With mailbot's event timeline, you can trace the entire lifecycle: customer email arrives (inbound event), your notification fires (notification delivered), your agent processes it and sends a reply (outbound event), the reply is delivered (delivery event), the customer opens it (open event). Every step is visible, timestamped, and inspectable.

When something breaks in that chain, you find it in seconds, not hours.

The Principle

Email infrastructure without observability is a liability. You can build the most sophisticated email automation in the world, but if you can't see what happened after you pressed send, you're operating blind. mailbot was built on the assumption that every email event should be a first-class, inspectable, replayable object. Not a log line. Not a status string. A structured event with a story to tell.