Skip to main content

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
Email 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.
WhatsApp 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.
Each chat channel has a dedicated configuration page under Settings → OutboundTelegram, Slack, Discord, Microsoft Teams, Google Chat, Matrix, Signal, and Supabase Realtime — alongside Email and Webhook.

Delivery flow

Every outbound message follows the same lifecycle regardless of channel:

  1. 1 Proposal created — the agent or workflow step generates an OutboundProposal with status pending. The proposal contains the rendered content, the target recipient, and the connector to use.
  2. 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. 3 Blacklist & rate-limit checks — before delivery, CheckBlacklist and the channel/target rate-limit middleware run. Blocked or throttled proposals are marked blocked or rate_limited.
  4. 4 DeliverySendOutbound dispatches the proposal via the connector and creates an OutboundAction recording the delivery attempt, timestamp, and status (sent, failed, or bounced).

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.
WhatsApp 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).
Test any connector before putting it into production with 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.

Proposals blocked by the blacklist are not retried. If you unblock a recipient after blocking, any already-blocked proposals must be re-queued manually or a new proposal must be generated.

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.
Tracking is opt-in per connector. Disable it by unchecking Enable tracking when configuring an Email connector. When disabled, pixel and click-wrapping are skipped entirely.

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.
bash
# 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"