Handing Email Monitoring to an AI Agent: Scheduled Checks, a State File, and a Three-Way Classification

2026-11-08来源 dev.to GitHubEnglish

You can miss an important email. But if you pick up every notification, you get buried in news that doesn't matter. If you live in your inbox, a time comes when both problems wear you out.

They look like separate problems, but they share a root: there is no defined process for checking email. When do you check? Up to what point have you checked? What gets reported, and what doesn't? Until those are decided, a person ends up guarding the inbox by willpower. Stop guarding and you miss things; keep guarding and notifications chase you.

Missed mail and noise seem to trade off: cut notifications and you worry about misses, add notifications and the noise grows. A shortcut out of this tug-of-war is to replace the person sitting in front of the inbox with a mechanism that wakes up on a schedule and follows the same procedure every time. A fixed procedure removes the need to decide at each check, so a person can spend their time only on the mail that actually needs a decision.

This article is for people who have just started running a personal AI agent as a long-lived process, and it describes a design for handing mail monitoring to that agent. It doesn't depend on any particular product. If you know what cron and scheduled runs are, you can read straight through.

Overview: wake up, resume where you left off, classify, advance the state

In our setup, the long-lived agent environment has a scheduled-run mechanism. It wakes up at a fixed interval and works through the items written in a checklist. The checklist is a plain Markdown file, and the interval can be changed in settings.

Mail monitoring is written as one item in that checklist. One pass goes like this:

  1. The scheduled run starts and opens the checklist
  2. Read the state file to learn how far the previous pass looked
  3. Fetch only mail newer than that timestamp
  4. Classify it with three rules
  5. Report only what should be reported, right then. Say nothing about the rest
  6. Advance the timestamp in the state file and leave one line of record for the pass

This check is read-only. It doesn't send mail, and it doesn't modify the mailbox. It looks, judges, and notifies. Limiting what you delegate to the agent to "look and notify" makes delegating much less worrying. Even a wrong judgment can't delete a message or fire off a reply to someone.

Three design decisions, plus two operating rules

The design decisions come down to three:

  • Interval: how often to wake up
  • Classification: what to report, what to ignore completely, and what to leave unreported
  • State: how to remember how far you've looked

Classification cleverness and model performance are not on this list. Decide these three first; any further refinement can be added once they are settled. Reverse the order and, however smart the classification, the check frequency and the resume point stay vague.

Below, we walk through these three using our actual setup. After that come two operating rules that sit on top of them.

1. Choose the interval by balancing speed against the number of passes

We started operating on 2026-10-27. The checklist at that time was the simplest form, and the user approved it. Since then the interval has changed twice.

Period Interval
Operation start (2026-10-27) 30 minutes
2026-10-30 1 hour
2026-11-05 180 seconds

It currently runs at a 180-second interval.

The change to 180 seconds came from the user's instruction: they wanted to get mail right away.

A note on honesty about the numbers: we have no measured record of detection latency. We can't write "it notices in X minutes on average." What the design values do let us say is that a check runs at most about every 3 minutes.

There is also a trade-off. A shorter interval means more frequent checks, but also more passes. Each pass reads the state file and fetches mail, so the shorter the interval, the more the cost of passes accumulates. The interval is a value you set between "I want to know early" and "the number of passes," and the right answer differs from person to person.

The interval can be changed in settings. We too started from the simplest form and changed it twice along the way.

2. Classify into three, not two

We classify into three: report, ignore completely, and stay silent about everything else. Not settling for a simple important/unimportant split is the crux of this design.

Report

These are reported one at a time, briefly:

  • GitHub review requests and assignments addressed to you (especially in repositories you're involved in)
  • Notices about deadlines, selection results, acceptance or rejection outcomes, and submissions
  • Mail about matters you are following

Ignore completely

These categories are treated as if they were never seen:

  • Some competitive programming notifications
  • Certain routine service notifications
  • Mail about a particular language exam
  • Solicitations from job-hunting, study-group, and event platforms
  • Ads and campaigns

In this article we name categories, not services. When you build your own list, write down the things that keep arriving in your inbox, by category.

Anything on the ignore list is used neither for reports nor for task candidates. Not allowing a "just in case" second look at those categories is what cuts the notification noise.

Stay silent about everything else

Mail that fits neither of the above is not reported. That is the default behavior. When in doubt, it isn't reported either.

There is exactly one exception: personal requests with a deadline are reported. Combining the default of staying silent with this exception is how we guard against both missed mail and noise.

Why three categories

With two categories, "unimportant" mixes together things you never want to use again and things that are merely not reported by default. Ignoring carries an extra rule, "not used for reports or task candidates," so it has to be kept apart from staying silent. With three categories, each one's handling stays distinct.

Why keep a separate ignore list

"Stay silent about everything else" alone already narrows what gets reported. We still keep the ignore list separately because the handling differs.

Staying silent is the default for whatever fits nowhere in the classification. Ignoring is a category you have additionally decided not to use for reports or task candidates. When a new kind of solicitation shows up, add that category to the ignore list.

3. Keep state in a file

How far you've looked is written to a state file. state.json has just two fields:

{ "last_checked_at": "2026-10-29T15:00:00+09:00", "note": "Scan 2026-10-29 15:00 JST: 2 new messages. Nothing reportable." } 

last_checked_at is a timestamp in ISO 8601 format with a timezone. It marks how far the previous pass looked. note is a string describing the latest scan (how many messages were seen and what was reported). The timestamp and note above are fictional examples, included to show the format.

The scan log

There is one more file, the scan log (last-scan.md). It is append-only, and each pass adds one line.

Scan: 2026-10-29 15:00 JST — checked messages newer than 2026-10-29 14:57 JST: 2 new messages. Nothing reported (both were in ignored categories) 

The format is "Scan: date-time — checked messages newer than previous timestamp: N new messages. Summary." This line also demonstrates the format; its date-time and message count are fictional.

With this file, you can trace later why something wasn't reported. It keeps not only what was reported but also what wasn't, and the reason. On a day when no notification arrives, you can check the log to see whether nothing really came or whether mail was seen and deliberately left unreported.

Resuming by timestamp

On each pass, only mail newer than last_checked_at is in scope. At the end of the pass, this timestamp is advanced to the time that pass ran. This approach has two aims:

  • The range already seen is left out of the next pass, so the same mail is not reported repeatedly
  • Mail that arrived after the previous timestamp is included in the next pass

Even if the agent restarts, or there is a stretch of downtime, it can resume from the timestamp left in the file by the same rule. That works because the state is kept in a file, not in the agent's memory.

There are other ways to decide what is in scope: going by whether mail is unread, or recording the IDs of processed messages to avoid duplicates. What we describe here is the timestamp-as-marker approach.

4. Report the current state, not the email event

After the three design points were settled, we added two rules during operation. The first, about the content of reports, was confirmed through a correction on 2026-10-27.

It is simple. In a report about a GitHub PR, don't relay the event written in the email (such as "approved") as it is. Check the PR's current state first, then report that. The state is one of: merged, closed, approved, or still open.

The reason is that the user is already reading the email itself on their phone right away. Relaying the email's event tells them what they already know, and a report about a stale event has no value. What the agent can add is an update on where things stand now.

A real example is in the 2026-10-27 scan log. We checked the PR that had triggered a review-request email and found that the user had already approved and merged it. So we decided to make neither a report nor a review draft.

This rule bears on report quality. What the recipient wants to know is less "what happened" than "what should I do now." If it's merged, there's nothing to do. If it's still open, they can judge whether any action is needed. The current state is the material for that judgment. The framing is that an email's arrival is the signal to start checking, not the content of the report.

Had we looked at the email alone, this PR would have been reported as a "review request came in" item. Checking the current state spared us the work of preparing both the report and the draft. That decision is also recorded in one line in the scan log mentioned above.

5. Beyond monitoring: preparing the review draft

The second rule adds preparation to the monitoring workflow. When a new GitHub review request is found, the same pass also prepares a review draft, so that a draft is already in hand by the time the user is notified.

Drafts are written following a written style guide built from past reviews. The guide has four points:

  • Start with an overall assessment
  • State each principle once, including its scope and exceptions
  • Check the entire text for each type of issue
  • State the limits of your judgment upfront

There are two storage rules. Save one draft per PR head SHA, and redo it when the SHA changes. When a PR is updated, the draft goes stale; keying on the SHA tells you which commit a draft was written for.

The guide is a written document, and drafts follow it. Because it was built from past reviews, drafts lean toward the way the user usually writes. All the user has to do is read the draft and choose to edit it or use it as is.

And drafts are only shown in chat. They are never posted to GitHub. The user checks the content and posts it themselves. That is where we drew the line on automation.

6. Checklist template

Here is everything so far in a form you can copy into your own environment. It shows only the classification frame and how state is kept. Replace the parenthesized parts with your own items.

## Mail monitoring Read last_checked_at in the state file (state.json) and target only mail newer than that. Read-only. Do not send mail or modify the mailbox. ### Report (one at a time, briefly) - (Review requests and assignments addressed to me) - (Notices about deadlines, selection results, outcomes, submissions) - (Mail about matters I am following) ### Ignore completely (use neither for reports nor for task candidates) - (Kinds of routine notifications that keep arriving) - (Kinds of solicitations) - (Ads and campaigns) ### Stay silent about everything else - Exception: report personal requests that have a deadline ### Before reporting - For a GitHub PR, check the PR's current state, not the event in the email, before reporting. Do not report what has already been handled - For a new review request that still needs review, prepare a draft following the style standard and save it per head SHA (redo it when the SHA changes). Show the draft in chat only; do not post it ### At the end of a pass - Advance last_checked_at in state.json and write the latest scan summary in note - Append one line to last-scan.md (date-time, number of messages seen, what was reported, and the reason for anything not reported) 

This template doesn't include the specific matters we follow. What to follow differs from person to person, so only the "matters I am following" slot is left.

One more convention for answering questions: when asked "any mail?", answer from the scan log first. Only re-check if the log is stale. If passes are running, the log is already the most recent answer.

7. Who this fits, and who it doesn't

Every part of this mechanism is small: a plain-text checklist, a two-field JSON file, and a one-line append-only record. No large infrastructure is needed. Let us close by saying who this design fits and who it doesn't.

It fits you if:

  • You already run a long-lived agent environment
  • You struggle with both missed important mail and too many notifications
  • You want to be able to trace later why something wasn't reported
  • You want to keep the final decision in your own hands

It does not fit you if:

  • You want replies and mailbox organization completed automatically. This design is read-only; it neither sends mail nor changes the mailbox
  • You prefer to go through the inbox all at once. This design assumes frequent, small checks
  • You don't yet have a long-lived agent environment. A scheduled-run mechanism is a prerequisite

Note that the numbers in this article are design values and dates only. Figures such as daily mail volume, classification accuracy, or time saved are not written here, because we have no records of them.

If you want to start, begin by writing the checklist in its simplest form. Our own setup began from the simplest form too. Start by recording the timestamp you have checked up to in a single file.

Originally published in Japanese on note. This English edition was translated and edited with AI assistance; all values and examples are unchanged from the original.

原文:https://www.a8q9.cn/en/article/handing-email-monitoring-to-an-ai-agent-scheduled-checks-a-state-file-and-a-thre-4b152a.html

相关文章