feat: send-email template registry + queued variant (closes #2064)#2067
Merged
Conversation
Extends `datamachine/send-email` with three optional inputs:
- `template` — id resolved via the new `datamachine_email_templates`
filter (`[id => callable( array $context ): string]`). The filter is
applied lazily inside execute() so consumers can hook at any priority
before the first call. Unknown ids return a structured failure.
- `context` — opaque array forwarded to the template callable.
- `mail_site_id` — when > 0 and multisite is active, wraps ONLY the
`wp_mail()` call in switch_to_blog() / restore_current_blog() so
site-scoped SMTP config applies. Invalid ids return a structured error.
Template rendering runs BEFORE placeholder replacement, so templates may
emit `{site_name}`, `{year}`, etc. Fully backwards-compatible: callers
that omit the new fields see no behavior change. `body` is no longer
hard-required at the schema level (`template` may substitute); execute()
enforces that at least one is supplied.
Refs #2064
New `datamachine/send-email-queued` ability defers delivery to Action Scheduler. Same payload shape as `datamachine/send-email` plus: - `send_at` — ISO 8601 string or unix timestamp. Empty enqueues async via as_enqueue_async_action; otherwise as_schedule_single_action. - `priority` — reserved hint, currently informational. Worker hook `datamachine_send_email_worker` invokes the underlying `datamachine/send-email` ability and, on failure, re-enqueues itself with a 5-minute backoff. Hard cap of 3 total attempts. Attempt counter rides in `_attempt` on the payload (internal, not part of the public schema). Group: `data-machine-email`. Wires the new ability into the plugin bootstrap and adds `wp datamachine email send-queued` mirroring `wp datamachine email send` with extra `--send-at`, `--priority`, `--template`, `--context` (JSON), and `--mail-site-id` flags. Adds tests/send-email-template-smoke.php exercising: - backward-compat raw body - template render + post-render placeholder replacement - unknown template structured error - mail_site_id switch_to_blog scoping + invalid id rejection - queued enqueue async + ISO 8601 send_at + invalid send_at - worker retry on failure with backoff + give up at MAX_ATTEMPTS - worker strips `_attempt` before forwarding All 35 assertions pass. Closes #2064
Contributor
Homeboy Results —
|
This was referenced May 18, 2026
refactor: migrate user emails to datamachine/send-email (closes #40)
Extra-Chill/extrachill-users#42
Merged
chubes4
added a commit
to Extra-Chill/extrachill-contact
that referenced
this pull request
May 18, 2026
…#3) Routes both admin notification and user confirmation through ec_send_email() (extrachill-multisite) instead of raw wp_mail(). - Admin email uses extrachill/minimal (no link grid, Reply-To preserved via the ability's reply_to input). - User confirmation uses extrachill/branded — the canonical EC link grid and footer markup that previously lived inline now ships from the extrachill/branded template, so the local HEREDOC + ec_get_site_url block is deleted. - function_exists guards keep the plugin safe to load when the multisite mail wrapper is not yet available. Sendy/Turnstile/REST handling is untouched. Depends on Extra-Chill/data-machine#2067 and Extra-Chill/extrachill-multisite#27 — opened as DRAFT until both land.
chubes4
added a commit
to Extra-Chill/extrachill-studio
that referenced
this pull request
May 18, 2026
) (#72) * refactor(email): migrate transcription email to ec_send_email, retire SMTP switch_to_blog Replaces raw wp_mail() in inc/transcription/callback.php with the ec_send_email() wrapper from extrachill-multisite. The DM datamachine/send-email ability now handles SMTP-site routing via the mail_site_id input (resolved by ec_mail_site_id()), so the manual switch_to_blog( ec_get_blog_id('main') ) wrapper around wp_mail — previously needed because Easy WP SMTP stores config per-site — is deleted. Two switch_to_blog calls remain in the file, both clearly commented as non-SMTP: - ec_studio_transcription_callback_create_draft(): post meta writes on main, because the draft lives there. - ec_studio_transcription_callback_send_email(): get_edit_post_link() resolution on main, for the same reason. Reduces inc/transcription/email-template.php to a pure inner-body renderer: the document chrome, greeting (Hey {recipient_name},), CTA button (Review draft), link grid, and footer are now owned by the extrachill/branded shell template. Transcription-specific content (filename, stats, preview) is passed as context.body_html; the edit URL + label are passed as context.cta_url + context.cta_label. Closes #71 Depends on Extra-Chill/data-machine#2067 and Extra-Chill/extrachill-multisite#27 — opened DRAFT until those land. * style: phpcs auto-fixes + translators comment for duration sprintf
chubes4
added a commit
to Extra-Chill/extrachill-shop
that referenced
this pull request
May 18, 2026
…template (#9) * refactor(email): route shipping-label email through ec_send_email + branded template The shop-create-shipping-label ability previously called back into extrachill_api_send_label_email() in extrachill-api — an upside-down dependency (the ability layer should not reach into the REST wrapper layer). Replace the callback with a self-contained branded email dispatched via ec_send_email() so the side-effect lives where it belongs. The new helper builds the order-specific HTML body (tracking, carrier, ship-to address, items, label download link) and hands it to the extrachill/branded template via context.body_html. SMTP routing / switch_to_blog is delegated to the underlying datamachine/send-email ability via the mail_site_id default applied by ec_send_email. Guards function_exists( 'ec_send_email' ) so the label purchase still succeeds when the foundation helper (extrachill-multisite#27) is not yet active. Refs Extra-Chill/extrachill-api#51 Depends on Extra-Chill/data-machine#2067 + Extra-Chill/extrachill-multisite#27 * style: phpcs auto-fixes + reorder declare(strict_types) after docblock
chubes4
added a commit
to Extra-Chill/extrachill-api
that referenced
this pull request
May 18, 2026
closes #51) (#54) The POST /shop/shipping-labels handler previously inlined Shippo calls, order meta writes, status transitions, and a raw wp_mail() dispatch directly in the REST layer. Per MEMORY.md the extrachill-api plugin is a thin REST wrapper — abilities own logic and side-effects. Collapse the handler to a wp_get_ability( 'extrachill/shop-create-shipping-label' )->execute() call, mirroring the existing GET handler at line 117. The ability in extrachill-shop now owns the full side-effect cluster, including the email dispatch which routes through ec_send_email() and the extrachill/branded template (companion PR in extrachill-shop). Delete extrachill_api_send_label_email() — no longer needed. Zero wp_mail() calls remain in inc/. Closes #51 Depends on Extra-Chill/data-machine#2067 + Extra-Chill/extrachill-multisite#27 Companion: extrachill-shop branch feat-51-shop-ability-email-side-effect
chubes4
added a commit
to Extra-Chill/data-machine-events
that referenced
this pull request
May 18, 2026
…loses #267) (#269) * refactor(email): migrate submission notification to datamachine/send-email Replace raw wp_mail() in SubmissionNotification with a direct call to the datamachine/send-email ability. This plugin is a DM extension, so it depends on DM core (already declared in Requires Plugins) and stays vendor-neutral — no extrachill-multisite coupling, no ec_send_email wrapper. Operators wire optional branded templates via two filters: * data_machine_events_submission_notification_template — return a template id registered through DM's datamachine_email_templates filter. Default '' sends the raw HTML body. * data_machine_events_submission_notification_context — merge or override the context array passed to that template. Defaults always include body_html, subject, event_id, event_title, event_url, submitter_name, submitter_email, site_name. If the ability is missing (DM disabled or too old), the notification is skipped and the failure is logged — no silent fallback to wp_mail. Depends on Extra-Chill/data-machine#2067. Closes #267. * style: mark intentional fallback error_log calls with phpcs:ignore --------- Co-authored-by: homeboy-ci[bot] <266378653+homeboy-ci[bot]@users.noreply.github.com>
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
Closes #2064.
Three additions, all backwards-compatible with the existing
extrachill-events→QualifyDigestAbilitiesconsumer:datamachine/send-email— new optionaltemplate+contextinputs. Templates are resolved via the newdatamachine_email_templatesfilter ([id => callable( array $context ): string]),applied lazily inside
execute()so consumers can hook at anypriority before the first call. Unknown ids return a structured
failure. Template render runs BEFORE placeholder replacement so
templates may emit
{site_name},{year}, etc.datamachine/send-email— new optionalmail_site_idinput. When > 0 and multisite is active, wraps ONLYthe
wp_mail()call inswitch_to_blog()/restore_current_blog().Validation, header building, and template rendering still run in the
original site context. Invalid blog ids and non-multisite environments
return a structured error.
datamachine/send-email-queuedability — same payload shape asdatamachine/send-emailplussend_at(ISO 8601 or unix timestamp)and
priority. Emptysend_atenqueues async viaas_enqueue_async_action; otherwiseas_schedule_single_action.Worker hook
datamachine_send_email_workerinvokes the synchronousability and re-enqueues on failure with a 5-minute backoff, hard
cap of 3 total attempts. Attempt counter rides in
_attempton thepayload (internal — not exposed in the public schema). AS group:
data-machine-email.Files touched
inc/Abilities/Publish/SendEmailAbility.phptemplate/context/mail_site_idinputs,datamachine_email_templatesfilter, switch_to_blog plumbinginc/Abilities/Publish/SendEmailQueuedAbility.phpdata-machine.phpSendEmailQueuedAbilityin bootstrapinc/Cli/Commands/EmailCommand.phpwp datamachine email send-queuedmirroringsendplus--send-at,--priority,--template,--context,--mail-site-idtests/send-email-template-smoke.phpSmoke test
Output:
Manual end-to-end check (CLI)
After deploy, the queued path can be exercised end-to-end with:
Vendor-name grep guard
Touched files are clean. Pre-existing vendor-name occurrences elsewhere in
inc/are out of scope for this PR.Backwards compatibility
The existing single consumer (
extrachill-events→QualifyDigestAbilities::send_digest_email()) callsdatamachine/send-emailwith onlyto/subject/body/cc/headers-equivalentfields. All new inputs are optional with sensible defaults:
template = ""→ unchanged raw-body flowcontext = []→ only consulted whentemplateis setmail_site_id = 0→ noswitch_to_blogExisting
wp datamachine email sendCLI and theinc/Api/Email.phpREST shim are not modified — they continue to work unchanged.
What's intentionally NOT in this PR
extrachill-multisite(separate issue).wp_mail()— one issue per plugin will follow.PermissionHelper::can_manage()stays for both abilities.wp datamachine email.cc <@532385681268408341> — ready for review.