Integrations
Mail Transports
Mail transports let MyronCMS send outbound email through a narrow provider contract. Core registers the built-in smtp transport at boot. Extensions can add provider-specific transports by returning transport instances from bootstrap.php.
Use this surface when an integration provider needs to make a delivery backend available to MyronCMS without changing form notification code or other mail callers.
Mental Model
The lifecycle has five parts:
- A developer authors a
MailTransportInterfaceimplementation underapp/extensions/{vendor}/. - The extension returns that instance from
bootstrap.phpundermailTransports. - The loader registers each local slug through
MailTransportRegistry::registerExtension(). - The registry stores extension transports under
x.{vendor}.{slug}. - Operators and scoped clients inspect known descriptors through the mail transport listing action.
The interface is intentionally small:
interface MailTransportInterface
{
public function send(MailEnvelope $envelope): MailDispatchResult;
public function describe(): array;
}
send() receives the normalized MailEnvelope. describe() returns the discovery metadata that the admin/API/MCP listing path exposes.
Human Path
A developer creates a provider transport class, updates the extension bootstrap.php, and has an operator register, enable, and grant the extension. The transport then appears in the registry when the extension loads.
Example bootstrap.php shape:
<?php
declare(strict_types=1);
require_once __DIR__ . '/mail/MyVendorApiTransport.php';
return [
'mailTransports' => [
'api' => new MyVendorApiTransport($sdk),
],
];
The local key api is not the final runtime id. The loader combines it with the extension vendor and registers:
x.my_vendor.api
AI Path
An AI coding agent can author the local PHP class, update bootstrap.php, and run repository verification. That is local authoring.
An installed MCP client cannot create the PHP files, enable the extension, grant scopes, or choose secrets for the provider. After the extension is loaded, an AI client with administration.read can inspect registered transport descriptors through:
GET /api/v1/administration/mail/transports
The same action is exposed to MCP, so a scoped MCP caller can discover it through tools/list and call the listed tool. The action returns a transports object keyed by registered transport id.
Scopes
Transport authoring is local and has no runtime caller scope by itself. Runtime inspection is gated:
| Surface | Scope |
|---|---|
/api/v1/administration/mail/transports | administration.read |
| MCP tool listing for the same action | administration.read |
| Extension setup actions, if the provider adds them | Whatever those actions declare |
Descriptor scopes should describe the setup or inspection scopes a provider needs. They do not grant the extension or the caller any permission.
Artifacts
Local artifacts:
app/extensions/{vendor}/mail/{TransportName}.phpapp/extensions/{vendor}/bootstrap.phpapp/extensions/{vendor}/manifest.json
Runtime artifacts:
MailTransportInterfaceMailTransportRegistry- registered id
x.{vendor}.{slug} GET /api/v1/administration/mail/transports- MCP
tools/listandtools/callfor the listing action when scoped
Safety Boundary
Do not hard-code provider API keys, SMTP passwords, signing secrets, bearer tokens, or webhook secrets in extension files. Extension files are source artifacts. Secrets belong in governed settings or another approved secret storage path introduced by a separate contract.
Do not register transports outside the extension namespace. Core transports use snake_case ids such as smtp; extension transports must use x.{vendor}.{slug} through the registry.
Do not treat descriptor visibility as send permission. A caller that can list descriptors has not gained the ability to configure a provider or bypass the mail settings workflow.
Verification
Run the foundation checks:
php app/tests/verify-extension-system-foundation.php
php app/tests/verify-forms-mail-substrate.php
For a loaded extension, verify:
- the extension is registered, enabled, and granted its manifest scopes,
- the bootstrap payload includes
mailTransports, knownTransports()includesx.{vendor}.{slug},GET /api/v1/administration/mail/transportsreturns the descriptor for anadministration.readcaller,- MCP
tools/listexposes the listing action only to a caller withadministration.read.
Failure Modes
| Failure | Likely cause | Safe response |
|---|---|---|
| Transport is absent from descriptors | Extension is not registered, disabled, under-granted, or did not return the transport from mailTransports. | Fix extension load state and bootstrap shape. |
| Load error mentions snake_case | Vendor or local transport slug failed registry validation. | Use lowercase snake_case for vendor and slug. |
| Descriptor appears but setup does not work | Descriptor metadata exists, but provider setup action or settings path is incomplete. | Verify the provider setup workflow and required scopes. |
| MCP caller cannot see the listing action | Caller lacks administration.read, or the action is not exposed in the running install. | Use a correctly scoped caller; do not broaden scopes automatically. |
| Provider secret appears in source | Secret was hard-coded into extension files. | Move the secret to an approved settings or secret storage path before use. |