Phase 5 follow-up: discover the \Sent mailbox; record the approved real send (AUD-71)
The approved real test reply was sent and the thread updated (same X-GM-THRID as the original). The live test exposed one bug: the thread check hardcoded Gmail's sent mailbox as "[Gmail]/Sent Mail", but this account exposes it as "[Google Mail]/Sent Mail". - internal/mailfetch: LIST parser, ListMailboxes, and SentMailbox that finds the mailbox flagged \Sent (RFC 6154) with name fallbacks. - internal/mailops: VerifyThreadUpdated selects the discovered \Sent mailbox instead of a hardcoded name. - cmd/email-agent: -check-thread <id> exercises the check against real Gmail without sending mail. - Tests for LIST parsing, sent-mailbox discovery, and the discovery error path. - docs/phase5-replies.md: record send-as verification, the sent reply's thread id, the app confirmation (sent_reply_in_thread=true), and the fix. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -1,15 +1,13 @@
|
||||
# Phase 5 — Reply drafts (templates, LLM draft, edit, send)
|
||||
|
||||
Status: **built, tested, and deployed. The one real test send is awaiting Sam's
|
||||
explicit approval** (issue interaction `080c500d-5968-49ad-9c40-0b2d3f445cc0` on
|
||||
[AUD-71](/AUD/issues/AUD-71)). Parent [AUD-68](/AUD/issues/AUD-68). Phase 5 of
|
||||
Status: **complete.** Built, tested, deployed, and the approved real test reply
|
||||
was sent and confirmed to have updated the thread. Sam approved the test send via
|
||||
issue interaction `080c500d-5968-49ad-9c40-0b2d3f445cc0` on
|
||||
[AUD-71](/AUD/issues/AUD-71). Parent [AUD-68](/AUD/issues/AUD-68). Phase 5 of
|
||||
AGENTS.md §11.
|
||||
|
||||
**DoD:** a reply is drafted, edited, and sent, and the thread updates.
|
||||
|
||||
Every part of the DoD except the real send is verified below. The real send needs
|
||||
Sam's approval per the phase rules and AGENTS.md §10; it is the only step that
|
||||
cannot be tested without sending mail.
|
||||
**DoD:** a reply is drafted, edited, and sent, and the thread updates. — met
|
||||
(§2.6).
|
||||
|
||||
## 1. What was built
|
||||
|
||||
@@ -84,19 +82,23 @@ and the send path is verified up to (but not including) DATA.
|
||||
|
||||
### 2.3 Gmail "Send mail as" verification (AGENTS.md §13 "Remaining")
|
||||
|
||||
AGENTS.md records send-as as "reported as already set up in Gmail … not verifiable
|
||||
from the sent folder". Gmail exposes no read-only alias list over IMAP/SMTP, so
|
||||
the only reliable check is a real send: Gmail accepts the `From` header only when
|
||||
the address is a registered send-as alias, and rejects it otherwise during DATA.
|
||||
AGENTS.md recorded send-as as "reported as already set up in Gmail … not
|
||||
verifiable from the sent folder". Gmail exposes no read-only alias list over
|
||||
IMAP/SMTP, so the check is a real send: Gmail accepts the `From` header only when
|
||||
the address is a registered send-as alias, and rejects it during DATA otherwise.
|
||||
|
||||
The SMTP connection, STARTTLS upgrade, and app-password authentication are
|
||||
verified (`-check-smtp`). The `From`-header/send-as check is **pending the
|
||||
approved test send**.
|
||||
**Result: verified.** The approved test reply was sent as
|
||||
`"Sam Rolfe" <hello@where-woof.com>` (the `X-Pm-Original-To` address) from the
|
||||
`samuelrolfe@gmail.com` account. Gmail accepted it and stored it in the Sent
|
||||
mailbox (`[Google Mail]/Sent Mail`) with that exact `From` header, so
|
||||
`hello@where-woof.com` is a working send-as alias. The SMTP connection, STARTTLS
|
||||
upgrade, and app-password authentication were already verified separately with
|
||||
`-check-smtp`.
|
||||
|
||||
### 2.4 Draft generated, edited, and stored (screenshot of the live data)
|
||||
### 2.4 Draft generated, edited, and sent
|
||||
|
||||
Draft `2` for message `99` ("Test email", arrived at `hello@where-woof.com`),
|
||||
generated through the live UI path and then edited:
|
||||
generated through the live UI path, edited, and then sent after Sam's approval:
|
||||
|
||||
```
|
||||
Thanks, got it. This came through to hello@where-woof.com. Since it's a test, no action needed on my side. If you need anything specific, let me know.
|
||||
@@ -104,13 +106,44 @@ Thanks, got it. This came through to hello@where-woof.com. Since it's a test, no
|
||||
Sam
|
||||
```
|
||||
|
||||
### 2.5 Thread update after a sent reply
|
||||
After the send the draft status is `sent`, the message left the inbox and now
|
||||
lives under the `Replied-to/hello@where-woof.com` label (1 row in the Replied-to
|
||||
view), and the message history shows `draft`, `reply_sent`, and the `move` rows
|
||||
(FR-13).
|
||||
|
||||
Pending the approved test send. The plan is to reply to message `99` as
|
||||
`"Sam Rolfe" <hello@where-woof.com>` to `samuelrolfe@gmail.com` (Sam himself — no
|
||||
third party), then confirm the thread by finding the original Gmail thread id in
|
||||
`[Gmail]/Sent Mail` (`X-GM-THRID` search). The exact message and draft are on the
|
||||
pending approval card.
|
||||
### 2.5 Thread update after the sent reply
|
||||
|
||||
**Result: verified — the reply joined the original thread.** Read directly from
|
||||
Gmail over IMAP (read-only):
|
||||
|
||||
| Message | From | To | `X-GM-THRID` | `In-Reply-To` / `References` |
|
||||
|---|---|---|---|---|
|
||||
| Original (`99`) | `sam rolfe <samuelrolfe@gmail.com>` | `hello@where-woof.com` | `1878738835102629816` | — |
|
||||
| Sent reply | `"Sam Rolfe" <hello@where-woof.com>` | `samuelrolfe@gmail.com` | `1878738835102629816` | `<CAMAgT7S04G5E=…@mail.gmail.com>` |
|
||||
|
||||
The reply carries the original `Message-ID` in `In-Reply-To` and `References` and
|
||||
Gmail assigned it the **same thread id**, so the thread updated. The app's own
|
||||
check now agrees:
|
||||
|
||||
```
|
||||
$ docker exec email-agent email-agent -check-thread 99
|
||||
message=99 thread=1878738835102629816 sent_reply_in_thread=true
|
||||
```
|
||||
|
||||
Status code `sent_reply_in_thread=true`. No third party received the test: the
|
||||
only recipient was `samuelrolfe@gmail.com`.
|
||||
|
||||
### 2.6 Bug found by the live test and fixed
|
||||
|
||||
The first send reported "Could not yet confirm the thread update". The cause was
|
||||
not threading: `VerifyThreadUpdated` hardcoded Gmail's mailbox as
|
||||
`[Gmail]/Sent Mail`, but this account exposes the special-use mailboxes as
|
||||
`[Google Mail]/…`. The fix discovers the mailbox flagged `\Sent` (RFC 6154) with
|
||||
`LIST`, falling back to the common names. New coverage:
|
||||
`internal/mailfetch` (`parseList`, `ListMailboxes`, `SentMailbox`) and
|
||||
`internal/mailops` (discovered sent mailbox, discovery error). `-check-thread
|
||||
<id>` was added so the check can be exercised against real Gmail without sending
|
||||
mail. After the fix, `-check-thread 99` returns true (above).
|
||||
|
||||
## 3. Configuration
|
||||
|
||||
@@ -129,9 +162,13 @@ docker compose up -d # migration v5 runs on start; data volume kept
|
||||
docker exec email-agent email-agent -check-smtp # safe: no mail sent
|
||||
```
|
||||
|
||||
Two commits were needed: the Phase 5 implementation and the `\Sent` mailbox
|
||||
discovery fix from §2.6.
|
||||
|
||||
## 5. Rollback
|
||||
|
||||
1. `git revert <phase-5 commit>` and `docker compose up -d --build`.
|
||||
1. `git revert <sent-mailbox-fix commit> <phase-5 commit>` and
|
||||
`docker compose up -d --build`.
|
||||
2. Migration v5 is additive (`messages.message_id_header`, `drafts.sent_at`); the
|
||||
Phase 4 binary ignores the new columns.
|
||||
3. A sent reply cannot be unsent. Gmail's Sent copy and the thread stay; the
|
||||
@@ -139,8 +176,6 @@ docker exec email-agent email-agent -check-smtp # safe: no mail sent
|
||||
|
||||
## 6. Not done / deferred
|
||||
|
||||
- **One real send + thread confirmation** — awaiting Sam's approval (the pending
|
||||
interaction above). This is the last DoD item.
|
||||
- A **template editor** (user-defined templates) is not in the Phase 5
|
||||
requirements; templates are built in. The Phase 6 rules editor still feeds the
|
||||
classifier.
|
||||
|
||||
Reference in New Issue
Block a user