Skip to main content

Transport Channels

PSAPLink Cloud supports 11 pluggable transport channels. All implement TransportHandlerInterface.

Available Transports

SlugProviderInbound ACK
emailSymfony Mailer (SMTP / SES / Postmark)✅ Yes
smsTwilio REST API✅ Yes
webhookHMAC-signed JSON HTTP POST✅ Yes
ntfyntfy.sh topic pushNo
slackSlack Incoming WebhooksNo
teamsMicrosoft Teams Connector CardsNo
pagerdutyPagerDuty Events API v2No
discordDiscord WebhooksNo
capCAP 1.2 XMLNo
pager_snppSNPP pager (RFC 1861)No
pushFCM / APNsNo

Transports that support Inbound ACK can receive replies from recipients and automatically mark notifications acknowledged.

Transport Interface

interface TransportHandlerInterface
{
public function getSlug(): string;
/** Send a notification. $config is already decrypted. */
public function send(NotificationDelivery $delivery, array $config): DeliveryResult;
/** Send a test message. $config is already decrypted. */
public function test(array $config): DeliveryResult;
/** Throw \InvalidArgumentException if config is invalid. */
public function validateConfig(array $config): void;
/** Parse inbound payload into AckResult. Return null if not applicable. */
public function parseInboundAck(array $payload): ?AckResult;
}

Credential Storage

Credentials are stored in transport_channels.config as a JSON blob encrypted at rest using libsodium. The transport handler receives the decrypted config array at send time. Credentials are never logged or returned in API responses.

Three-Level Config Hierarchy

LevelWhere storedWho controls
Platform defaultstransport_types.platform_configsuperadmin
Per-channel configtransport_channels.configagency admin
Per-user overridesuser_transport_preferences.config_overridecompany user

All three levels are encrypted.

Adding a Custom Transport

  1. Create a class in cloud/src/Transport/ implementing TransportHandlerInterface
  2. Tag the service with psaplink.transport
  3. Add credentials schema to the transport registry
  4. Add a doc page in this section