feat: add email handler — send and fetch emails#831
Merged
Conversation
Adds email as a core handler with the abilities-first pattern:
Send side (publish):
- SendEmailAbility (datamachine/send-email): wp_mail() with attachments,
CC/BCC, placeholder support ({month}, {year}, {site_name}, {date})
- Email publish handler with AI tool (email_send): configurable defaults
for recipients, subject, content type — AI can override per execution
- EmailSettings: 8 configurable fields (to/cc/bcc, from, subject, etc.)
Fetch side (inbox):
- FetchEmailAbility (datamachine/fetch-email): IMAP inbox retrieval with
search criteria, body parsing (plain text preferred), attachment download
- Email fetch handler with IMAP auth provider (EmailAuth): credential
storage via BaseAuthProvider, sender/subject/timeframe filtering
- EmailFetchSettings: 10 fields including folder, search criteria, filters
Closes #341
Homeboy Results —
|
…abilities
Adds the complete application layer for email:
Abilities (5 new):
- datamachine/email-reply: threaded replies with In-Reply-To/References
- datamachine/email-delete: IMAP delete + expunge
- datamachine/email-move: move messages between IMAP folders
- datamachine/email-flag: set/clear flags (Seen, Flagged, Answered, etc.)
- datamachine/email-test-connection: verify IMAP credentials + list folders
CLI (wp datamachine email):
- send: send email with placeholders and attachments
- fetch: retrieve inbox messages with search/filter/format options
- reply: threaded reply with message threading headers
- delete: delete message by UID (with confirmation prompt)
- move: move message to different folder
- flag: set/clear IMAP flags
- test-connection: verify IMAP setup and list available folders
REST API (datamachine/v1/email):
- POST /email/send
- GET /email/fetch
- POST /email/reply
- DELETE /email/{uid}
- POST /email/{uid}/move
- POST /email/{uid}/flag
- POST /email/test-connection
All endpoints delegate to abilities — zero business logic in wrappers.
…ults
Performance and usability improvements for the email toolkit:
- headers_only mode: fast header-only fetch — skips body parsing,
structure fetching, and attachment scanning. Enables browsing
81K+ inboxes in seconds instead of timing out.
- Single message read by UID: fetch full body of one message
for detail view (wp datamachine email read <uid>).
- Pagination: offset parameter for paging through large result
sets. Response includes total_matches, offset, has_more.
- UIDs exposed: every item now includes uid in metadata for
use with delete/move/flag/read operations.
- Flags in output: seen (read/unread) and flagged (starred)
status in header-only results.
- CLI improvements: --headers-only flag, --offset flag, default
fields include uid, pagination summary in table output.
- REST improvements: GET /email/{uid}/read endpoint, headers_only
and offset params on /email/fetch.
Adds batch operations for inbox management at scale: Abilities (3 new): - datamachine/email-batch-move: move all matches to folder - datamachine/email-batch-flag: set/clear flag on all matches - datamachine/email-batch-delete: delete all matches All batch ops use comma-separated UID sets for IMAP operations (single connection, single command — fast even for thousands). Safety: - Max limits: move/flag default 500, delete default 100 - All CLI commands require confirmation (--yes to skip) - batch-delete warns 'This cannot be undone' - No 'ALL' shortcut — requires explicit IMAP search criteria CLI commands: - wp datamachine email batch-move --search=... --destination=... - wp datamachine email batch-flag --search=... <flag> - wp datamachine email batch-delete --search=... REST endpoints: - POST /email/batch/move - POST /email/batch/flag - POST /email/batch/delete
Adds List-Unsubscribe support per RFC 8058:
Abilities (2 new):
- datamachine/email-unsubscribe: parse List-Unsubscribe header from
a message and execute via One-Click POST, URL GET, or mailto
- datamachine/email-batch-unsubscribe: search → deduplicate by sender
→ unsubscribe from each unique sender using most recent message
Unsubscribe priority order:
1. List-Unsubscribe-Post + URL → HTTP POST (RFC 8058 one-click)
2. URL without Post header → HTTP GET
3. mailto: → send email via wp_mail()
Batch deduplicates by sender — 100 emails from linkedin.com results
in 1 unsubscribe, not 100.
CLI:
- wp datamachine email unsubscribe <uid>
- wp datamachine email batch-unsubscribe --search=...
REST:
- POST /email/{uid}/unsubscribe
- POST /email/batch/unsubscribe
Tested live: Dominos (One-Click POST, HTTP 202) and LinkedIn
(5 sender addresses, all One-Click POST success).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Ability → Handler → AI ToolArchitecture
Send Side
SendEmailAbilityinc/Abilities/Publish/SendEmailAbility.phpwp_mail()with attachments, headers, placeholder replacementEmailhandlerinc/Core/Steps/Publish/Handlers/Email/Email.phpEmailSettingsinc/Core/Steps/Publish/Handlers/Email/EmailSettings.phpAI Tool:
email_send— LLM composes subject + body (HTML), optionally overrides recipients and attaches filesPlaceholders:
{month},{year},{site_name},{date},{admin_email}in subject and bodyTransport: Uses
wp_mail()— respects FluentSMTP, WP Mail SMTP, or any mail plugin the site has configuredFetch Side
FetchEmailAbilityinc/Abilities/Fetch/FetchEmailAbility.phpEmailhandlerinc/Core/Steps/Fetch/Handlers/Email/Email.phpEmailAuthinc/Core/Steps/Fetch/Handlers/Email/EmailAuth.phpBaseAuthProviderEmailFetchSettingsinc/Core/Steps/Fetch/Handlers/Email/EmailFetchSettings.phpDedup: Uses RFC 2822
Message-IDheader asdedup_key— standard DM processed items systemBody parsing: Prefers
text/plain, falls back to strippedtext/html. Handles multipart/alternative and nested parts.IMAP search: Supports standard IMAP criteria (
UNSEEN,ALL,FROM "x",SINCE "date") augmented by handler config filtersPipeline Patterns Enabled
Testing
Tested on chubes.net via WP-CLI:
wp_get_ability())datamachine_handlersfilter)email_imap)email_send)Closes #341