Skip to content

feat(mail): multi-provider mail framework with connections, routing, delivery webhooks, and failure alerts - #37

Merged
abdul-kaioum merged 6 commits into
feat/multi-provider-mail-frameworkfrom
fm/smtp-routing-plugin-picker
Aug 20, 2026
Merged

abdul-kaioum merged 6 commits into
feat/multi-provider-mail-frameworkfrom
fm/smtp-routing-plugin-picker

Conversation

@abdul-kaioum

Copy link
Copy Markdown
Member

Intent

Email-routing feature (lives only on branch feat/multi-provider-mail-framework, NOT main): make the routing-rule condition whose field is 'Source plugin' offer a searchable picker of wp_mail sender plugins detected at runtime (WooCommerce, Bit Form, Contact Form 7, ...), replacing the blind free-text box, while STILL allowing an admin to type a plugin slug not yet detected. The saved rule value MUST remain the plugin slug string (routing matches on the slug), never the friendly label. Every OTHER field (recipient, from, subject) keeps the existing plain Input. Switching the field into or out of source_plugin resets condition.value to '' so no stale value leaks across modes.

Backend: MailAnalyticsRepository::distinctSourcePlugins() — all-time DISTINCT source_plugin read excluding NULL/empty/'unknown', ordered asc, static SQL, no interpolated input (reuses rows() convention). MailSourceLabeler maps slug->label via get_plugins() on the plugin dir prefix, falling back to raw slug, passing mu:/theme: through. MailSourceController::index returns { sources:[{value,label}] } mirroring ProviderController::index; route GET mail/routing/sources inside the existing cap:admin Route::group (admin-gated, read-only). Deliberate decision: did NOT add an AbilitySchemas output schema — that class is consumed only by AbilitiesProvider (WP abilities/MCP), not by plain REST controllers, so a schema here would be unconsumed dead code; ProviderController::index likewise has none.

Frontend: useMailSources hook (react-query + @common/helpers/request, GET mail/routing/sources) mirroring useProviders; MailSource {value,label}. ConditionEditor renders antd AutoComplete (searchable via filterOption on slug+label) for source_plugin, plain Input otherwise. sources threaded RoutingRulesPage->SortableRuleRow->RoutingRuleRow->ConditionEditor like the existing connections prop. Additive only.

Tests: ConditionEditor.test (AutoComplete vs Input, emits slug, allows custom slug, clears value on switch), RoutingRuleRow/RoutingRulesPage prop threading + detected-source surfacing, useMailSources.test, PHP unit MailSourceLabelerTest, PHP integration MailSourceControllerTest (distinct read, {sources} shape, admin-gated route). DB test lives in tests/Integration because composer test:unit only runs tests/Unit+tests/Golden.

Constraint: PR MUST target base feat/multi-provider-mail-framework, never main. Local gates already green: pnpm test/lint/build, composer test:unit/lint/compat/analyse.

What Changed

  • Replace the single global SMTP config (removes backend/app/Connectors/SmtpConfig.php and the Homepage SMTP form) with a multi-provider Mail domain under backend/app/Mail/: 14 providers (Amazon SES, Gmail, Microsoft 365, SendGrid, Mailgun, Brevo, Postmark, Resend, Mailjet, SparkPost, ZeptoMail, Cloudflare, Other SMTP, PHP sendmail) built on descriptor/transport/auth-strategy layers, AES-256-GCM credential encryption at rest, an OAuth2 connect flow, and a Connections management UI with drag-ordered fallback.
  • Add per-connection smart routing that dispatches wp_mail by source plugin, recipient, subject, or from (drag-reorderable rules), provider delivery-status webhooks with per-provider signature verification and event rollup surfaced on mail-log rows, failure-notification alerts across email/Slack/Telegram/webhook channels, and mail analytics exposed through WP abilities.
  • Give the routing "Source plugin" condition a searchable picker of wp_mail sender plugins detected at runtime — new admin-gated GET mail/routing/sources route backed by MailSourceController/MailSourceLabeler, with the saved rule value kept as the plugin slug while still allowing custom slugs — and add split PHPUnit unit/integration/golden suites, a PHPStan level-6 baseline, and a Playwright e2e suite wired into CI.

Risk Assessment

✅ Low: Additive, well-bounded feature (searchable source-plugin picker) that fully conforms to the stated intent, uses static non-interpolated SQL backed by an existing index, keeps the saved value as the slug, and is covered by behavior-based FE and PHP tests with no correctness, security, or performance defect found.

Testing

Baseline setup (pnpm install, composer install) plus targeted automated tests all pass: 4 Routing vitest suites (25 tests) prove the AutoComplete-vs-Input switch, slug emission, custom-slug entry, value-reset on field-mode change, prop threading, and the mail/routing/sources hook; the PHP unit MailSourceLabelerTest (3 tests) proves slug->label mapping and the raw-slug fallbacks. For the UI-facing intent I rendered the real ConditionEditor in a browser via Vite + Playwright and captured two reviewer-visible screenshots showing the searchable picker (WooCommerce/Bit Form/Contact Form 7), label+slug filtering, an accepted custom typed slug, the saved value remaining the slug string, and the recipient field keeping its plain Input. The PHP integration test (DB DISTINCT read + admin-gated route) was not run because it depends on the project's docker composer test:integration harness (DB :3307 + WordPress core) absent from this isolated worktree and not gated by CI; that slice is owned by the docker path / outer pipeline. Temp demo files and the dev server were removed; the worktree is clean and evidence screenshots remain in the evidence directory.

  • Evidence: Source plugin field → AutoComplete picker; Recipient field → plain Input (unchanged) (local file: /home/ar2/.no-mistakes/evidence/01M0EZ5MSSCGR9P58WXE7MA90E/picker-open-dropdown.png)
  • Evidence: Typing 'form' filters detected sources by slug+label (WooCommerce dropped) and saved condition.value = "form" — custom slug allowed, value stays the slug string not the label (local file: /home/ar2/.no-mistakes/evidence/01M0EZ5MSSCGR9P58WXE7MA90E/picker-filtered-and-custom.png)
  • Outcome: ⚠️ 1 info across 1 run (8m6s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

⚠️ **Test** - 1 info
  • ℹ️ tests/Integration/MailSourceControllerTest.php:1 - PHP integration test MailSourceControllerTest (backend DISTINCT source_plugin read excluding null/empty/'unknown' ordered asc, {sources:[{value,label}]} shape, and admin-gated GET mail/routing/sources route registration) was reviewed but not executed here: it requires the project's docker composer test:integration harness (test DB on 127.0.0.1:3307, currently down) plus a WordPress core checkout (absent in this isolated worktree). No CI workflow runs test:integration, so this slice is owned by that docker path / outer pipeline. The user-facing picker intent and the labeler slug->label logic are fully covered by the frontend behavior tests, visual evidence, and the passing PHP unit test, so this does not block demonstrating the change.
  • npx vitest --watch=false frontend/src/pages/Routing/ConditionEditor.test.tsx frontend/src/pages/Routing/RoutingRuleRow.test.tsx frontend/src/pages/Routing/RoutingRulesPage.test.tsx frontend/src/pages/Routing/data/useMailSources.test.tsx — 4 files, 25 tests passed
  • ./vendor/bin/phpunit -c phpunit.xml.dist --filter MailSourceLabeler — 3 tests passed
  • Manual visual: served the real ConditionEditor via a throwaway Vite entry and drove it with Playwright — confirmed source_plugin field renders an antd AutoComplete picker (value=''), recipient field renders the unchanged plain Input; captured screenshot picker-open-dropdown.png
  • Manual visual: typed 'form' into the source_plugin picker — dropdown filtered to Bit Form + Contact Form 7 (WooCommerce dropped, matching on slug+label) while saved condition.value became the literal typed string 'form' (custom slug allowed, value is the slug not the label); captured screenshot picker-filtered-and-custom.png
  • Reviewed tests/Integration/MailSourceControllerTest.php; confirmed it requires docker DB :3307 (down) + WP core (absent) so it runs only under composer test:integration
🔧 **Document** - 1 issue found → auto-fixed ✅
  • ℹ️ AGENTS.md:47 - AGENTS.md 'Example' lists 'Request Middleware in backend/app/HTTP/Requests', but middleware actually lives in backend/app/HTTP/Middleware/. Pre-existing factual error NOT surfaced by this change (no middleware added), so left untouched to keep scope tight; worth a small follow-up fix.

🔧 Fix: correct stale middleware path in AGENTS.md example
✅ Re-checked - no issues remain.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Replace the blind free-text box with a searchable AutoComplete listing the
wp_mail sender plugins detected at runtime, while still allowing an admin to
type a slug not yet detected. The saved rule value stays the plugin slug.

Backend adds an all-time distinct source read (MailAnalyticsRepository::
distinctSourcePlugins) excluding empty/unknown senders, a slug->name mapper
over get_plugins (MailSourceLabeler), and an admin-gated read endpoint
GET mail/routing/sources returning { sources: [{ value, label }] }.

Frontend threads the sources list RoutingRulesPage -> RoutingRuleRow ->
ConditionEditor via useMailSources, and resets condition.value when the field
switches into/out of source_plugin so no stale value leaks across modes.

Assisted-By: AI
Comment on lines +77 to +105
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ['8.0', '8.1', '8.3']
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 1 }
- name: Setup PHP ${{ matrix.php }}
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
tools: composer:v2
# phpunit 12 requires PHP >= 8.3, so 8.0/8.1 install with the platform check ignored and skip test:unit/analyse.
- name: Install deps (ignore platform for the 8.0/8.1 gate legs)
run: composer install --prefer-dist --no-progress ${{ matrix.php != '8.3' && '--ignore-platform-req=php' || '' }}
- name: Gate smoke (all versions)
run: php tests/gate/gate-smoke.php
- name: Bootstrap-chain 8.0 compat
run: composer compat:gate
- name: Backend 8.1 compat (8.1 and 8.3)
if: matrix.php != '8.0'
run: composer compat
- name: Full PHP unit suite incl. golden (8.3 only)
if: matrix.php == '8.3'
run: composer test:unit
- name: PHPStan (8.3 only)
if: matrix.php == '8.3'
run: composer analyse
@github-actions

Copy link
Copy Markdown

🔍 WordPress Plugin Check Report

❌ Status: Failed

📊 Report

🎯 Total Issues ❌ Errors ⚠️ Warnings
73 56 17

❌ Errors (56)

📁 backend/app/Mail/Providers/AbstractWebhookProvisioner.php (5 errors)
📍 Line 🔖 Check 💬 Message
42 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '__'.
42 WordPress.WP.I18n.NonSingularStringLiteralText The $text parameter must be a single text string literal. Found: \sprintf('%s webhook creation requires a %s connection.', $expected, $expected)
76 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '$response'.
76 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '$webhookUrl'.
110 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '$providerLabel'.
📁 backend/app/HTTP/Controllers/OAuthController.php (8 errors)
📍 Line 🔖 Check 💬 Message
139 WordPress.Security.EscapeOutput.OutputNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '$html'.
205 WordPress.WP.I18n.MissingTranslatorsComment A function call to __() with texts containing placeholders was found, but was not accompanied by a "translators:" comment on the line above to clarify the meaning of the placeholders.
205 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '__'.
205 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '$provider'.
210 WordPress.WP.I18n.MissingTranslatorsComment A function call to __() with texts containing placeholders was found, but was not accompanied by a "translators:" comment on the line above to clarify the meaning of the placeholders.
210 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '__'.
210 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '$provider'.
242 PluginCheck.CodeAnalysis.Heredoc.NotAllowed Use of heredoc syntax (<<<) is not allowed; use standard strings or inline HTML instead
📁 backend/app/HTTP/Controllers/ConnectionController.php (1 error)
📍 Line 🔖 Check 💬 Message
35 WordPress.WP.I18n.MissingTranslatorsComment A function call to __() with texts containing placeholders was found, but was not accompanied by a "translators:" comment on the line above to clarify the meaning of the placeholders.
📁 languages/generatedString.php (4 errors)
📍 Line 🔖 Check 💬 Message
32 WordPress.WP.I18n.MissingTranslatorsComment A function call to __() with texts containing placeholders was found, but was not accompanied by a "translators:" comment on the line above to clarify the meaning of the placeholders.
42 WordPress.WP.I18n.MissingTranslatorsComment A function call to __() with texts containing placeholders was found, but was not accompanied by a "translators:" comment on the line above to clarify the meaning of the placeholders.
58 WordPress.WP.I18n.MissingTranslatorsComment A function call to __() with texts containing placeholders was found, but was not accompanied by a "translators:" comment on the line above to clarify the meaning of the placeholders.
62 WordPress.WP.I18n.MissingTranslatorsComment A function call to __() with texts containing placeholders was found, but was not accompanied by a "translators:" comment on the line above to clarify the meaning of the placeholders.
📁 backend/app/Mail/Message/MimeBuilder.php (2 errors)
📍 Line 🔖 Check 💬 Message
29 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '$e'.
29 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '$e'.
📁 backend/app/Mail/Providers/SendGrid/SendGridTransport.php (1 error)
📍 Line 🔖 Check 💬 Message
188 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '"Unable to read attachment file: {$path}"'.
📁 backend/app/Mail/Providers/ProviderRegistry.php (2 errors)
📍 Line 🔖 Check 💬 Message
23 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '$key'.
32 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '$key'.
📁 backend/app/Mail/Providers/Mailgun/MailgunWebhookService.php (2 errors)
📍 Line 🔖 Check 💬 Message
75 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '__'.
87 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '__'.
📁 backend/app/Mail/Connections/Connection.php (1 error)
📍 Line 🔖 Check 💬 Message
57 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '"Missing required key: {$required}"'.
📁 backend/app/Mail/OAuth/OAuth2TokenProvider.php (1 error)
📍 Line 🔖 Check 💬 Message
62 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '$this'.
📁 backend/app/Mail/Descriptor/ProviderDescriptor.php (1 error)
📍 Line 🔖 Check 💬 Message
91 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '"ProviderDescriptor missing required key: {$required}"'.
📁 backend/app/Mail/Descriptor/DescriptorApiTransport.php (3 errors)
📍 Line 🔖 Check 💬 Message
199 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '"Unknown address source: {$source}"'.
372 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '$key'.
376 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '$key'.
📁 backend/app/Mail/Descriptor/DescriptorProvider.php (1 error)
📍 Line 🔖 Check 💬 Message
133 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '"Unsupported encoder: {$encoder}"'.
📁 backend/app/Mail/Auth/AuthorizationResolver.php (2 errors)
📍 Line 🔖 Check 💬 Message
64 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '"No HTTP auth strategy for type: {$type}"'.
73 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found 'get_class'.
📁 backend/app/Mail/Auth/AbstractAuthStrategy.php (1 error)
📍 Line 🔖 Check 💬 Message
31 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '$key'.
📁 backend/app/Mail/Auth/AwsSigV4Strategy.php (2 errors)
📍 Line 🔖 Check 💬 Message
40 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '$region'.
47 WordPress.WP.AlternativeFunctions.parse_url_parse_url parse_url() is discouraged because of inconsistency in the output across PHP versions; use wp_parse_url() instead.
📁 backend/app/Mail/Support/AttachmentBuilder.php (3 errors)
📍 Line 🔖 Check 💬 Message
74 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '"Unknown attachment shape: {$shape}"'.
98 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '"Unknown attachment shape: {$shape}"'.
107 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '"Unable to read attachment file: {$path}"'.
📁 backend/app/Mail/Support/MultipartEncoder.php (1 error)
📍 Line 🔖 Check 💬 Message
72 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '"Unable to read attachment file: {$path}"'.
📁 backend/app/Mail/Support/AddressFormatter.php (1 error)
📍 Line 🔖 Check 💬 Message
49 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '"Unknown address shape: {$shape}"'.
📁 backend/app/HTTP/Services/WebhookProvisioningService.php (2 errors)
📍 Line 🔖 Check 💬 Message
72 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '__'.
101 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '__'.
📁 backend/db/Migrations/BitSmtpLogsTableMigration.php (5 errors)
📍 Line 🔖 Check 💬 Message
154 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '$column'.
173 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '$index'.
202 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '$index'.
211 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '$operation'.
211 WordPress.Security.EscapeOutput.ExceptionNotEscaped All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '$error'.
📁 backend/app/Mail/Notifications/FailureNotificationMessage.php (1 error)
📍 Line 🔖 Check 💬 Message
110 WordPress.WP.AlternativeFunctions.strip_tags_strip_tags strip_tags() is discouraged. Use the more comprehensive wp_strip_all_tags() instead.
📁 backend/app/Mail/Notifications/Channels/SlackFailureNotificationChannel.php (1 error)
📍 Line 🔖 Check 💬 Message
61 WordPress.WP.AlternativeFunctions.parse_url_parse_url parse_url() is discouraged because of inconsistency in the output across PHP versions; use wp_parse_url() instead.
📁 backend/app/Mail/Aws/SigV4Signer.php (1 error)
📍 Line 🔖 Check 💬 Message
36 WordPress.WP.AlternativeFunctions.parse_url_parse_url parse_url() is discouraged because of inconsistency in the output across PHP versions; use wp_parse_url() instead.
📁 backend/app/Mail/Config/MailSettingsSanitizer.php (2 errors)
📍 Line 🔖 Check 💬 Message
278 WordPress.WP.AlternativeFunctions.parse_url_parse_url parse_url() is discouraged because of inconsistency in the output across PHP versions; use wp_parse_url() instead.
352 WordPress.WP.AlternativeFunctions.parse_url_parse_url parse_url() is discouraged because of inconsistency in the output across PHP versions; use wp_parse_url() instead.
📁 backend/app/HTTP/OAuth/OAuthCallbackRouter.php (1 error)
📍 Line 🔖 Check 💬 Message
95 WordPress.WP.AlternativeFunctions.parse_url_parse_url parse_url() is discouraged because of inconsistency in the output across PHP versions; use wp_parse_url() instead.
📁 readme.txt (1 error)
📍 Line 🔖 Check 💬 Message
0 outdated_tested_upto_header Tested up to: 6.9 < 7.1. The "Tested up to" value in your plugin is not set to the current version of WordPress. This means your plugin will not show up in searches, as we require plugins to be compatible and documented as tested up to the most recent version of WordPress.

⚠️ Warnings (17)

📁 backend/app/HTTP/Services/WebhookProvisioningService.php (1 warning)
📍 Line 🔖 Check 💬 Message
174 WordPress.PHP.DevelopmentFunctions.error_log_error_log error_log() found. Debug code should not normally be used in production.
📁 backend/app/Mail/Config/MailSettingsSanitizer.php (1 warning)
📍 Line 🔖 Check 💬 Message
479 WordPress.PHP.DevelopmentFunctions.error_log_error_log error_log() found. Debug code should not normally be used in production.
📁 backend/app/HTTP/OAuth/OAuthCallbackRouter.php (4 warnings)
📍 Line 🔖 Check 💬 Message
61 WordPress.Security.ValidatedSanitizedInput.MissingUnslash $_SERVER['REQUEST_URI'] not unslashed before sanitization. Use wp_unslash() or similar
61 WordPress.Security.ValidatedSanitizedInput.InputNotSanitized Detected usage of a non-sanitized input variable: $_SERVER['REQUEST_URI']
68 WordPress.Security.ValidatedSanitizedInput.MissingUnslash $_SERVER['REQUEST_METHOD'] not unslashed before sanitization. Use wp_unslash() or similar
68 WordPress.Security.ValidatedSanitizedInput.InputNotSanitized Detected usage of a non-sanitized input variable: $_SERVER['REQUEST_METHOD']
📁 readme.txt (3 warnings)
📍 Line 🔖 Check 💬 Message
0 mismatched_plugin_name Plugin name "Bit SMTP – Easy SMTP Solution with Email Logs" is different from the name declared in plugin header "Bit SMTP".
0 readme_parser_warnings_trimmed_section_description The "Description" section is too long and was truncated. A maximum of 2500 characters is supported.
0 readme_parser_warnings_trimmed_short_description The "Short Description" section is too long and was truncated. A maximum of 150 characters is supported.
📁 backend/app/Mail/Routing/MailSourceDetector.php (1 warning)
📍 Line 🔖 Check 💬 Message
11 WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace debug_backtrace() found. Debug code should not normally be used in production.
📁 backend/app/HTTP/Services/MailConfigService.php (1 warning)
📍 Line 🔖 Check 💬 Message
482 WordPress.PHP.DevelopmentFunctions.error_log_error_log error_log() found. Debug code should not normally be used in production.
📁 backend/app/HTTP/Webhook/WebhookRouter.php (6 warnings)
📍 Line 🔖 Check 💬 Message
56 WordPress.Security.ValidatedSanitizedInput.MissingUnslash $_SERVER['REQUEST_URI'] not unslashed before sanitization. Use wp_unslash() or similar
56 WordPress.Security.ValidatedSanitizedInput.InputNotSanitized Detected usage of a non-sanitized input variable: $_SERVER['REQUEST_URI']
66 WordPress.Security.ValidatedSanitizedInput.MissingUnslash $_SERVER['REQUEST_METHOD'] not unslashed before sanitization. Use wp_unslash() or similar
66 WordPress.Security.ValidatedSanitizedInput.InputNotSanitized Detected usage of a non-sanitized input variable: $_SERVER['REQUEST_METHOD']
74 WordPress.Security.ValidatedSanitizedInput.MissingUnslash $_SERVER['CONTENT_LENGTH'] not unslashed before sanitization. Use wp_unslash() or similar
74 WordPress.Security.ValidatedSanitizedInput.InputNotSanitized Detected usage of a non-sanitized input variable: $_SERVER['CONTENT_LENGTH']

🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check

@abdul-kaioum
abdul-kaioum changed the base branch from main to feat/multi-provider-mail-framework August 20, 2026 07:29
Add a pull_request trigger on feat/*|feature/* bases so PRs targeting the
multi-provider feature branch (not just pushes) run the PHP suite; the repo
previously ran CI only on PRs to main, leaving feature-branch PRs unchecked.

Replace the hardcoded php: ['8.0','8.1','8.3'] matrix with a php-matrix job
that derives the floor from the "Requires PHP" header in bit_smtp.php and emits
floor->latest-stable as JSON (8.1..8.4), dropping the legacy 8.0 leg. php-tests
consumes it and preserves per-version behavior: legs below 8.3 install with
--ignore-platform-req=php and skip test:unit/analyse (phpunit 12 needs 8.3+);
compat:gate runs on the minimum leg; compat runs on all legs.

Assisted-By: AI
@abdul-kaioum
abdul-kaioum merged commit 767de67 into feat/multi-provider-mail-framework Aug 20, 2026
10 checks passed
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.

2 participants