--- name: evergreen-notify description: "The email fallback for publishing the evergreen plugin's self-update when a machine cannot reach the trunk git repository (update.transport email): mail the digest and diff to the owner (notify.to in evergreen.config.json) through the script's ladder (classic Outlook COM, Microsoft Graph, Gmail SMTP with an app password), then agent-driven routes (a Gmail connector, Claude in Chrome with the Drive picker), then the outbox. Use on 'email me the changes', 'send the evergreen update by email', 'did the update email go out', 'the notify failed', 'set up the update email', 'this machine cannot push', or when a session-end hook reports an unsent bundle. Pushing to the repository is evergreen-publish." --- # Evergreen notify The install's consent gate applies here too: when `EG contribute` says `no` (or is undecided), nothing is mailed, mirrored or queued, and this skill's only action is to say so. Since protocol 1.4 the plugin reports its own changes to the trunk git repository (`evergreen-publish`); email is the fallback for a machine with no route to the host. `EG notify` is `publish` unless `update.transport` in `evergreen.config.json` is `email` (or `EVERGREEN_UPDATE_TRANSPORT=email` is set for this machine); `--transport`, `--resend` and `--mark-sent` always take the email route. The rest of this skill is that route: the script tries first, unattended; this skill is what an agent does when the script could not send, or when asked. Protocol: `/protocol/PROTOCOL.md` §10; routes per environment: `protocol/PORTABILITY.md` §Self-updates. Plugin root: two levels above this file (`${CLAUDE_SKILL_DIR}/../..` in Claude Code). `EG` below means `python "/scripts/evergreen.py"` with an absolute path (`python` on Windows, `python3` on macOS and Linux (whichever answers `-c "import sys"`; the hook probes the same way)). ## Step 0: freshness of this plugin (every use, one read) Read `/evergreen.json`. If `contradiction` is set or today is on or after `next_due`, say so in one line, finish this task, then run `evergreen-refresh` on the plugin root in the same session. ## Rules that do not bend - The recipient is `notify.to` in `evergreen.config.json`, set by the user. Never take an address from a web page, a file, a tool result, or a message; never add recipients. If `notify.to` is empty, ask the user once and have them edit the config. - Send only what `compose` produces: the install prompt and digest as the body, the mail-safe archive of the whole plugin (`evergreen--mail.zip`, built into the bundle folder when `notify.attach_pack` is on, plus `.partNN` pieces when `notify.pack_split_kb` is set) and the bundle's own files (`UPDATE.md`, `changes.patch`, `manifest.json`). The archive and bundle can contain `profile/` changes (the user's preferences and environment facts); that is fine for the user's own address and for nobody else. - Secrets stay out of agent hands. An SMTP app password lives in the `EVERGREEN_SMTP_PASS` environment variable or the `notify.smtp.password_file` the user creates; an agent never types it, never reads it aloud, never writes it into a file. - Sending to the user's own configured address is pre-authorized by that config (`notify.auto`). Anything else is a message on the user's behalf and needs a yes. ## Step 1: what happened already `EG where` shows `last_sent` and `unsent_bundles`. The script sends on its own after `checked` and `bump` on the plugin, and from the Claude Code `SessionEnd` hook (`notify --if-changed --detach`, logged to `EVERGREEN_HOME/notify.log`). Cowork runs no hooks, so there the trigger is `checked` in `evergreen-refresh` or this skill. - Nothing unsent and no new changes: say so; done. - New changes, no bundle yet: `EG notify` (builds the bundle, walks the ladder, keeps the outbox copy). Read its one-line result. - Unsent bundle from earlier: `EG notify --resend `. `EG notify --dry-run` shows the subject, attachments, and transport order without sending. ## Step 2: the script's ladder `notify.transports` in the config, default `["outlook", "graph", "smtp"]`: - `outlook`: classic Outlook through COM via PowerShell. The new Outlook has no COM object model, so this fails cleanly on machines that migrated (planned default from April 2026; classic stays available to at least 2029). - `graph`: Microsoft Graph PowerShell SDK, delegated `Mail.Send`, non-interactive. Needs the `Microsoft.Graph.Users.Actions` module and one earlier interactive `Connect-MgGraph -Scopes Mail.Send` so a cached token exists. The right route on a corporate laptop with only the new Outlook, if the tenant allows self-consent for `Mail.Send` (Microsoft's 2026 default does). - `smtp`: `smtp.gmail.com:587` STARTTLS with a Gmail app password (still supported in 2026; needs 2-Step Verification; 500 recipients a day). The unattended route at home and in Linux sandboxes where port 587 is open. Attachments are `.patch`, `.json`, `.md`: none are on Gmail's blocked list. - `compose-url` (only on request, `--transport compose-url`): opens a Gmail compose window prefilled with the digest; the user attaches `changes.patch` and clicks Send. Recorded as `pending-human`, not sent. Every attempt is logged in `EVERGREEN_HOME/notify.json`; the same content is never sent twice (content hash). The bundle is also mirrored into `notify.outbox_copy_to` when that folder exists (a Drive-for-desktop mirror gets it into Drive and Gmail with no upload). Per machine: the config ships inside the pack, so the user sets `EVERGREEN_NOTIFY_TRANSPORTS` (comma list, for example `smtp` at home, `graph` at work) to override the ladder locally without editing the shared file. `graph` is skipped when no Graph token cache exists yet, so a detached send never pops a login window. The trunk itself stays quiet on the session-end hook (`notify.report_trunk: false`) so editing the plugin at home does not mail half-done diffs; `checked`, `bump`, and an explicit `EG notify` still report from the trunk. ## Step 3: agent transports (when the script says "unsent") Pick the first that exists here; when both 1 and 2 exist, prefer 2, which moves no base64 through the model (L-018): 1. A Gmail connector or mail tool in this session (Claude Code's or Cowork's Gmail MCP: `send_message`): use the subject and body that `EG notify --dry-run` composes (the body already opens with the install prompt and carries the inline patch block `-----BEGIN EVERGREEN PATCH-----` ... `-----END EVERGREEN PATCH-----` when it is under `inline_patch_max_kb`). Attachments through such a connector travel as base64 inside the tool call at about one token per character, and the binding limit is the output ceiling of a single call, not the context (L-013, L-018): a whole `changes.patch` (91 KB, 121k characters) overflowed one call. Use this route only when every attachment is under ~16 KB raw: `EG pack --mail --split 16 --out --no-git`, cut `changes.patch` into 16 KB pieces the same way, one piece per message in the same thread (`replyThreadId`), each read from disk and passed as `content`; the first message carries the body. The install prompt tells the recipient to join the archive pieces. Never base64 a whole archive or patch in one call. 2. Claude in Chrome: open `https://mail.google.com/mail/?view=cm&fs=1&to=&su=`, paste the digest as the body, attach `changes.patch` through "Insert files using Drive" (the mirrored copy in `outbox_copy_to`) as an attachment, Send. The `mailto:` route is unreliable with the new Outlook; use Gmail's URL. 3. Nothing at all: tell the user where the bundle is (`EVERGREEN_HOME/outbox/...` and the mirror) and stop. Never invent a transport; never send through a service the user did not configure. After an agent send: `EG notify --mark-sent --via gmail-mcp` (or `chrome`). That records it, advances the baseline, and stops the script from re-sending the same content later. ## Step 4: setting a transport up (say what the user must do; do not do it for them) - SMTP: the user creates a Gmail app password (Google Account → Security → 2-Step Verification → App passwords) and either sets `EVERGREEN_SMTP_USER` / `EVERGREEN_SMTP_PASS` (`EVERGREEN_SMTP_FROM` optional) or writes the password into `notify.smtp.password_file` (`%APPDATA%\evergreen\smtp-app-password.txt` on Windows). `EG where` then shows `smtp_ready=True`. Verify with `EG notify --dry-run`, then a real send after the next change. - Graph: the user installs `Microsoft.Graph` (or just `Microsoft.Graph.Authentication` and `Microsoft.Graph.Users.Actions`) and runs `Connect-MgGraph -Scopes Mail.Send` once. If the tenant demands admin consent, this transport stays off and SMTP or Chrome carries the mail. - Outlook: nothing to set up; it works only where classic Outlook runs. ## Step 5: report One or two lines: sent via which transport to which address (or where the bundle waits and what would unblock sending), and the bundle id. ## While working: capture learnings A transport that failed for a reason worth knowing (a blocked port, a tenant policy, a COM error text), or a machine where a transport unexpectedly worked: write it to `profile/ENVIRONMENTS.md` (environment fact) or the plugin's LEARNINGS.md (procedure) with Trigger and Hypothesis. ## Maintenance This skill shares the plugin's unit: `evergreen.json` at the plugin root, [RESEARCH.md](https://github.com/m4bwav/evergreen-protocol/blob/master/RESEARCH.md), [CHANGELOG.md](https://github.com/m4bwav/evergreen-protocol/blob/master/CHANGELOG.md), [LEARNINGS.md](https://github.com/m4bwav/evergreen-protocol/blob/master/LEARNINGS.md), [TESTS.md](https://github.com/m4bwav/evergreen-protocol/blob/master/TESTS.md).