Outbound Delivery
Outbound Delivery is the mechanism by which FleetQ sends content to the outside world. When an agent or workflow produces a result intended for a person or system, it creates an OutboundProposal — a pending delivery record that travels through approval, rate-limiting, and blacklist checks before being dispatched via the configured connector.
Scenario: A sales automation experiment analyses a new enterprise lead and drafts a personalised outreach email. The proposal waits for a sales manager to approve it in the Approval Inbox. Once approved, the email is sent via the team's custom SMTP connector. Open and click events are tracked automatically and fed back into the experiment's metrics.
Supported channels
Every channel below is a first-class core driver registered on the
OutboundConnectorManager — no plugin required. Eight chat
channels join Email, Webhook, and ntfy, and each has its own per-channel configuration page under
Settings → Outbound.
| Channel | Description |
|---|---|
| Send via the platform's shared SMTP relay or your own SMTP/Resend connector. Supports HTML and plain-text bodies, attachments, reply-to headers, and open/click tracking. | |
| Telegram |
Send messages to Telegram chats or channels via a registered bot token and
chat_id. Supports Markdown
formatting and inline buttons.
|
| Slack | Post to Slack channels via an incoming webhook URL or an OAuth bot token. Supports Block Kit message formatting. |
| Discord | Post to a Discord channel via a channel webhook URL. |
| Microsoft Teams | Post to a Teams channel via an incoming webhook (connector) URL. |
| Google Chat | Post to a Google Chat space via a space webhook URL. |
| Matrix | Send to a Matrix room via a homeserver URL, access token, and room ID. |
| Signal |
Send via a self-hosted signal-cli REST gateway (the
signal_protocol channel).
|
| Supabase Realtime | Broadcast the proposal payload to a Supabase Realtime channel for in-app delivery. |
Send messages via the WhatsApp Business API. Supports plain text and
WhatsApp message templates. Requires a
phone_number_id and a
bearer token from the Meta Developer Portal.
|
|
| ntfy | Push to an ntfy topic for lightweight mobile/desktop notifications. |
| Webhook | HTTP POST the proposal payload to any URL. Configure custom headers and an auth method (bearer token, basic auth, HMAC signature, or none). |
| Custom |
Implement the OutboundConnectorInterface
and register it as a driver — $manager->extend('custom', …) —
to add any delivery channel.
|
Delivery flow
Every outbound message follows the same lifecycle regardless of channel:
-
1
Proposal created — the agent or workflow step
generates an
OutboundProposalwith statuspending. The proposal contains the rendered content, the target recipient, and the connector to use. - 2 Approval gate — if the experiment or project requires human approval, the proposal waits in the Approval Inbox until a team member approves or rejects it. If auto-approve is enabled the proposal advances immediately.
-
3
Blacklist & rate-limit checks — before
delivery,
CheckBlacklistand the channel/target rate-limit middleware run. Blocked or throttled proposals are markedblockedorrate_limited. -
4
Delivery —
SendOutbounddispatches the proposal via the connector and creates anOutboundActionrecording the delivery attempt, timestamp, and status (sent,failed, orbounced).
Connector configuration
Connectors are configured per team at Settings → Outbound Connectors or via the API. Each connector stores its credentials encrypted using the team's per-team key.
| Connector | Required configuration |
|---|---|
| Email (platform SMTP) | From address only — uses the platform's shared relay. |
| Email (custom SMTP) | SMTP host, port, username, password, encryption (tls/ssl/none), from address. |
| Telegram |
Bot token (from @BotFather),
default chat_id (can be overridden per proposal).
|
| Slack | Incoming webhook URL or OAuth bot token with a default channel name. |
phone_number_id (numeric, from Meta Business),
token (bearer access token).
Set whatsapp_template in the proposal metadata
to send a pre-approved message template instead of free-form text.
|
|
| Webhook | Target URL, HTTP method (POST/PUT), custom headers (JSON object), auth method (none / bearer / basic / hmac-sha256). |
POST /api/v1/outbound-connectors/{id}/test
or the Test button in the UI. A synthetic payload will be delivered
and the result returned in the response.
Rate limiting
Two independent rate-limit layers protect against accidental flooding:
| Layer | Middleware | What it limits |
|---|---|---|
| Channel rate limit | ChannelRateLimit | Total deliveries per channel type per time window — e.g. max 100 emails/hour across all recipients. Configurable per connector. |
| Target rate limit | TargetRateLimit | Deliveries to a specific recipient address per time window — e.g. max 5 emails/day to a single email address. Prevents spamming individual contacts. |
When a limit is hit, the proposal is marked rate_limited
and automatically retried after the window resets. Limits are stored in Redis and reset atomically.
Blacklist
The CheckBlacklist action runs before every delivery
and compares the recipient against the team's blocked list. Matched proposals are immediately
marked blocked — no delivery is attempted and no
retry is scheduled.
Manage blocked recipients at Settings → Outbound → Blacklist. Entries can be
individual addresses, domains (e.g. @example.com),
or phone numbers.
Email tracking
Email opens and link clicks are tracked automatically when using the Email connector.
FleetQ injects a 1×1 tracking pixel and rewrites links before delivery. Events are
recorded against the OutboundAction and available
in experiment metrics.
| Event | Endpoint | Behaviour |
|---|---|---|
| Open | GET /api/track/pixel | Returns a transparent 1×1 GIF. Records the open event with timestamp and approximate geo/user-agent data. |
| Click | GET /api/track/click | Records the click event, then issues a 302 redirect to the original URL. The recipient's browser lands on the intended destination without delay. |
MCP tools
All outbound connector management is exposed as MCP tools, allowing AI agents to configure and test delivery channels autonomously.
| Tool | Description |
|---|---|
| connector_config_list | List all outbound connectors configured for the team. |
| connector_config_get | Retrieve the full configuration for a specific connector by ID. |
| connector_config_save | Create or update a connector. Accepts channel type and credential fields. |
| connector_config_delete | Delete a connector by ID. Fails if active proposals reference it. |
| connector_config_test | Send a test payload through the connector and return the delivery result. |
API endpoints
All endpoints require a Sanctum bearer token. Full OpenAPI documentation is available at /docs/api.
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/outbound-connectors | List all connectors (cursor-paginated). |
| GET | /api/v1/outbound-connectors/{id} | Retrieve a single connector. |
| POST | /api/v1/outbound-connectors | Create a new connector. |
| PUT | /api/v1/outbound-connectors/{id} | Update connector configuration. |
| DELETE | /api/v1/outbound-connectors/{id} | Delete a connector. |
| POST | /api/v1/outbound-connectors/{id}/test | Send a test message and return the delivery result. |
# Create a Slack connector
curl -X POST https://fleetq.169.58.89.204.sslip.io/api/v1/outbound-connectors \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Sales Slack",
"channel": "slack",
"config": {
"webhook_url": "https://hooks.slack.com/services/T000/B000/xxxx",
"default_channel": "#sales-alerts"
}
}'
# Test it
curl -X POST https://fleetq.169.58.89.204.sslip.io/api/v1/outbound-connectors/{id}/test \
-H "Authorization: Bearer YOUR_TOKEN"