Skip to content

Inbound channels

An inbound channel is an external conversation bound to a folder — a Telegram chat, a Slack channel, an email thread — that a plugin relays into Filer as if it were a chat message, and relays results back out of.

A channel plugin typically starts its own polling or webhook loop from an onAppReady hook, receives a message from the external service, and hands it to Filer with ctx.triggerInbound. This requires the hostTrigger permission — see Permissions. The payload’s fields are channelId, sourcePlugin, messageId and content, plus an optional attachments array of { filename, contentBase64 }.

// hooks/initialize.js — registered as "onAppReady" in the manifest
export default async function onAppReady(event, ctx) {
startPolling(async (channelId, nativeId, messageText) => {
await ctx.triggerInbound({
channelId,
sourcePlugin: 'my-plugin',
messageId: `my-plugin-${nativeId}`,
content: messageText,
});
});
}

The convention is <plugin>-<nativeId> — your plugin’s name, then whatever id the external service already uses for that message, so the pairing is stable and reproducible if the same message arrives twice:

Service Native id Status
Telegram update.update_id official plugin
Email the Message-ID header, used as-is (it is already globally unique); email-<uid> when a message has none official plugin
Slack event.ts suggested convention
Discord message.id suggested convention

The official Telegram and Email plugins relay inbound messages this way; the Slack and Discord rows are the ids those services expose that fit the same pattern, not something an official plugin sends.

ctx.listChannels() returns the channels currently registered, which a plugin uses to build a sender → channel map — for example, mapping a Telegram chat id to the Filer channel it’s bound to. ctx.channels.getIntegrationConfig(channelId, pluginName) reads per-channel configuration your plugin previously stored with setIntegrationConfig.

Once a message has been triggered into a folder and the agent responds, use the onSessionMessage hook (see Hooks and events) to catch that response and relay it back to the external service — posting a reply to the same Telegram chat, for instance.

If the agent raises a HITL approval request mid-turn, the onAgentHitlRequest hook fires with { agentId, executionId, requestId, action, target, description, channelId }. A channel plugin can relay that description out to the external service (asking the human on the other end to approve or deny) and then call ctx.respondToHitl(agentId, requestId, approved, reason) with their answer.

The official Telegram plugin implements this whole pattern end to end — polling, messageId construction, onSessionMessage relay, and HITL relay. See github.com/iyulab/filer-official-plugins.