Integrations
Build A Provider Transport
This tutorial shows the shape of a provider-specific mail transport. It is a local extension authoring task. MyronCMS core currently ships the smtp transport; provider transports are extension candidates.
The example uses a fictional API provider so the contract stays focused on extension shape instead of one vendor's SDK.
Prerequisites
- An extension package exists under
app/extensions/my_vendor/. - The extension manifest declares any scopes needed by provider setup or inspection.
- Provider credentials are available through an approved settings path, not source code.
- The operator can register, enable, and grant the extension.
Human Path
Create a transport class:
<?php
declare(strict_types=1);
final class MyVendorApiTransport implements MailTransportInterface
{
public function __construct(private readonly MyronExtensionSDK $sdk)
{
}
public function send(MailEnvelope $envelope): MailDispatchResult
{
$apiKey = $this->loadApiKey();
if ($apiKey === '') {
return MailDispatchResult::failed('x.my_vendor.api', 'mail_provider_not_configured');
}
// Build the provider request from the normalized envelope.
$payload = [
'to' => $envelope->to,
'subject' => $envelope->subject,
'html' => $envelope->htmlBody,
'text' => $envelope->textBody,
'from' => [
'email' => $envelope->fromEmail,
'name' => $envelope->fromName,
],
'replyTo' => $envelope->replyTo,
];
// Send the payload with an approved HTTP client/settings path.
$providerMessageId = 'provider-message-id';
return MailDispatchResult::ok('x.my_vendor.api', $providerMessageId);
}
public function describe(): array
{
return [
'slug' => 'x.my_vendor.api',
'label' => 'My Vendor API',
'description' => 'API-based transactional mail transport.',
'scopes' => [MyronApiScopes::ADMINISTRATION_READ],
'capabilities' => ['api', 'html', 'text', 'tags'],
];
}
private function loadApiKey(): string
{
// Read from an approved settings or secret path. Do not hard-code secrets here.
return '';
}
}
Return it from bootstrap.php:
<?php
declare(strict_types=1);
require_once __DIR__ . '/mail/MyVendorApiTransport.php';
return [
'mailTransports' => [
'api' => new MyVendorApiTransport($sdk),
],
];
The registry validates the vendor and local slug as snake_case and registers the runtime id:
x.my_vendor.api
AI Path
An AI coding agent can create the class, update bootstrap.php, and run verification locally.
An installed MCP client cannot create files, enable the extension, grant scopes, or supply its own trust gate. After the transport loads, a scoped AI client can inspect descriptor metadata through the administration mail transport listing action.
Scopes
Separate setup scopes from send-time behavior:
| Concern | Documentation requirement |
|---|---|
| Descriptor listing | Requires administration.read through /api/v1/administration/mail/transports. |
| Provider setup UI or action | Declare the read/write administration scopes that setup actually needs. |
| Sending through saved settings | The transport uses the normalized envelope and approved saved configuration. |
If the provider adds setup actions, those actions need their own metadata, schemas, side effects, and scope gates. The transport descriptor does not replace action metadata.
Descriptor Fields
describe() should return:
| Field | Meaning |
|---|---|
slug | Fully qualified transport id, such as x.my_vendor.api. |
label | Human-readable provider label. |
description | Short provider and delivery-mode summary. |
scopes | Setup or inspection scopes relevant to this transport. |
capabilities | Small capability tags such as api, smtp, html, text, tags, batch, or webhooks. |
Use capabilities as discovery hints. Do not document them as guaranteed behavior unless the transport implements that behavior.
Artifacts
Local files:
app/extensions/my_vendor/mail/MyVendorApiTransport.phpapp/extensions/my_vendor/bootstrap.phpapp/extensions/my_vendor/manifest.json
Runtime surfaces:
MailTransportInterface::send()MailTransportInterface::describe()MailDispatchResult::ok()MailDispatchResult::failed()GET /api/v1/administration/mail/transports
Safety Boundary
Do not hard-code secrets in the transport class, manifest, bootstrap file, docs examples, or tests. A provider implementation should fail closed with a stable error such as mail_provider_not_configured when settings are absent.
Do not claim that this example provider ships in core. Core v1 registers SMTP; provider transports are extension-authored.
Do not silently swallow provider failures. Return MailDispatchResult::failed() with a stable error string that callers and operators can act on.
Verification
Run:
php app/tests/verify-extension-system-foundation.php
php app/tests/verify-forms-mail-substrate.php
Then inspect the loaded descriptor as an administration.read caller:
curl -sS \
-H "Authorization: Bearer ${MYRON_TOKEN}" \
"https://example.test/api/v1/administration/mail/transports"
Verify:
- the descriptor key is
x.my_vendor.api, - the descriptor includes
slug,label,description,scopes, andcapabilities, - missing provider settings return a failed result instead of leaking or guessing secrets,
- an under-scoped MCP caller cannot discover the listing action.
Failure Modes
| Failure | Likely cause | Safe response |
|---|---|---|
| Extension load fails | Vendor or transport slug is not snake_case, or bootstrap has a PHP error. | Fix namespace shape and run extension verification. |
| Transport descriptor is missing | Extension is unregistered, disabled, under-granted, or did not return mailTransports. | Check Installed Extensions and load errors. |
Send returns mail_provider_not_configured | Provider settings or secrets are absent. | Complete the approved setup path; do not put secrets in source. |
| Provider returns an error | Remote delivery failed or rejected the payload. | Map it to MailDispatchResult::failed() with a stable error. |
| Descriptor visible to human but not MCP caller | MCP caller lacks administration.read. | Use a correctly scoped caller; do not broaden scope from the client. |