Skip to content

feat: add email handler — send and fetch emails#831

Merged
chubes4 merged 5 commits intomainfrom
feature/email-handler
Mar 17, 2026
Merged

feat: add email handler — send and fetch emails#831
chubes4 merged 5 commits intomainfrom
feature/email-handler

Conversation

@chubes4
Copy link
Member

@chubes4 chubes4 commented Mar 17, 2026

Summary

  • Adds email send (publish handler) and email fetch (IMAP inbox) to Data Machine core
  • Follows the abilities-first pattern: Ability → Handler → AI Tool
  • Both send and fetch work through the WordPress Abilities API from day 1 — callable from REST, CLI, chat, and pipelines

Architecture

SendEmailAbility (datamachine/send-email)
  ↑ Email Publish Handler (email_publish)
    ↑ AI Tool: email_send

FetchEmailAbility (datamachine/fetch-email)
  ↑ Email Fetch Handler (email)
    ↑ IMAP Auth Provider (email_imap)

Send Side

Component File Purpose
SendEmailAbility inc/Abilities/Publish/SendEmailAbility.php Core logic: wp_mail() with attachments, headers, placeholder replacement
Email handler inc/Core/Steps/Publish/Handlers/Email/Email.php Pipeline handler: merges config defaults with AI params, delegates to ability
EmailSettings inc/Core/Steps/Publish/Handlers/Email/EmailSettings.php 8 configurable fields: to/cc/bcc, from name/email, reply-to, subject, content type

AI Tool: email_send — LLM composes subject + body (HTML), optionally overrides recipients and attaches files

Placeholders: {month}, {year}, {site_name}, {date}, {admin_email} in subject and body

Transport: Uses wp_mail() — respects FluentSMTP, WP Mail SMTP, or any mail plugin the site has configured

Fetch Side

Component File Purpose
FetchEmailAbility inc/Abilities/Fetch/FetchEmailAbility.php Core logic: IMAP connection, search, body parsing, attachment download
Email handler inc/Core/Steps/Fetch/Handlers/Email/Email.php Pipeline handler: resolves IMAP creds from auth, builds search criteria
EmailAuth inc/Core/Steps/Fetch/Handlers/Email/EmailAuth.php IMAP credential storage via BaseAuthProvider
EmailFetchSettings inc/Core/Steps/Fetch/Handlers/Email/EmailFetchSettings.php 10 fields: folder, search, from/subject filters, mark as read, download attachments

Dedup: Uses RFC 2822 Message-ID header as dedup_key — standard DM processed items system

Body parsing: Prefers text/plain, falls back to stripped text/html. Handles multipart/alternative and nested parts.

IMAP search: Supports standard IMAP criteria (UNSEEN, ALL, FROM "x", SINCE "date") augmented by handler config filters

Pipeline Patterns Enabled

# Content digest
RSS Fetch → AI (summarize) → Email Publish (weekly digest)

# Inbox triage
Email Fetch (UNSEEN) → AI (classify) → Email Publish (auto-reply)

# Report delivery
WP Posts Fetch → AI (analyze) → Email Publish (send report)

Testing

Tested on chubes.net via WP-CLI:

  • ✅ Both abilities registered (wp_get_ability())
  • ✅ Both handlers registered (datamachine_handlers filter)
  • ✅ Settings fields returned (8 publish, 10 fetch)
  • ✅ Auth provider registered (email_imap)
  • ✅ AI tool registered (email_send)
  • ✅ Email sent successfully with placeholder resolution

Closes #341

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
@github-actions
Copy link

github-actions bot commented Mar 17, 2026

Homeboy Results — data-machine

Lint

⚡ Scope: changed files only

lint (changed files only)

Test

⚡ Scope: changed files only

test (changed files only)

Audit

⚡ Scope: changed files only

audit (changed files only)

Tooling versions
  • Homeboy CLI: homeboy 0.79.0+04e914f6
  • Extension: wordpress from https://github.com/Extra-Chill/homeboy-extensions
  • Extension revision: unknown
  • Action: Extra-Chill/homeboy-action@v1

Homeboy Action v1

chubes4 added 4 commits March 17, 2026 00:58
…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).
@chubes4 chubes4 merged commit 293c009 into main Mar 17, 2026
3 checks passed
@chubes4 chubes4 deleted the feature/email-handler branch March 17, 2026 03:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Email handler: fetch inbox + send/publish emails

1 participant