Telegram & Slack Setup
TaskView messaging integrations deliver task events to messengers. There are two levels of connection:
- Personal — a user links their own account and receives direct messages about their tasks (assigned, deadline, etc.).
- Project — an admin connects a group/channel, and events for that project are posted to it for the whole team.
The bot credentials are configured per instance (via environment variables), so the official TaskView SaaS ships an official bot and self-hosted installs supply their own — no code changes, only configuration.
Notification model & event delivery
Understanding who receives what matters, because it is not "every subscriber gets every event".
Connection scope
- Personal connections are keyed to a user (
ownerType = user), not to a project or organization. One personal connection covers all your projects and organizations — switching the organization you view in the app does not change delivery. You receive DMs about your tasks everywhere you are a member. - Project connections are keyed to a project (
ownerType = project). They post that project's events to the connected channel/group for the whole team.
Per-event audience (personal delivery)
Each event is delivered to a specific audience. A subscriber receives it only if they are in that audience — the subscription checkbox controls whether you get an event you're eligible for, not who is eligible.
| Event | Personal recipients |
|---|---|
| Task created | all project members (a new task usually has no assignees yet) |
| Task assigned | the task's assignees |
| Task status changed | the task's assignees |
| Task completed / reopened | the task's assignees |
| Sprint / member / time-tracking events | the relevant project members / actors |
Key rules:
- No initiator exclusion. Messaging is an explicit opt-in feed, so you receive an event even for your own action — e.g. completing a task you're assigned to still DMs you. The initiator is not filtered out; they simply receive the event if they are in the audience.
- Consequence: for everything except Task created, you are notified only if you are an assignee of that task. A task with no assignees produces no personal completed/status notification for anyone (there is no one in the audience). "Not an assignee → not notified" applies to the initiator too — they are just one non-assignee among others.
- Task reopened is delivered under the Task completed subscription (same checkbox), shown with a
[Task reopened]title and a ↩️→ Done button.
Project delivery & content gating
- Project events go to the connected channel/group when the connection is active and subscribed to that event.
- Delivery is queued (pg-boss) and retried with backoff (up to 3 attempts).
The post content toggle
A shared channel is not an RBAC principal — TaskView can't check the permissions of each channel member (they may not even have a TaskView account). So instead of per-viewer gating, a project connection has one explicit switch, post content, set by whoever connected the channel (requires INTEGRATIONS_CAN_MANAGE). It decides how much of a task event is broadcast:
post content = true (default) | post content = false | |
|---|---|---|
| Task title + link | ✅ posted | ✅ posted |
| Task description | ✅ posted | ❌ hidden |
Assignee footer (👤 emails) | ✅ posted | ❌ hidden |
| Action buttons (Done / Reopen / Assign) | ✅ shown | ❌ hidden |
Turn it off for a channel whose audience should see only that something happened (title + link) without the task's content, assignee emails, or interactive actions. The event itself is still delivered either way — only the content and buttons are suppressed. Buttons are hidden on a content-hidden channel because the Assign action opens a modal listing project members, which would defeat the point of hiding content.
Personal DMs are different: each recipient is a known TaskView user, so the description is gated per recipient by
COMPONENT_CAN_WATCH_CONTENTand the assignee footer byTASKS_CAN_WATCH_ASSIGNED_USERS— and action buttons are always shown (any action still runs under that user's own RBAC).
Prerequisites
- TaskView API running and reachable over public HTTPS (messengers deliver updates/redirects to your API;
localhostis not reachable from Telegram/Slack — use your production domain or a tunnel for local testing) - PostgreSQL database with migrations applied
.env.taskviewfile configured
Local testing tunnel: prefer
cloudflared tunnel --url http://localhost:1401— it gives a clean HTTPS URL with no interstitial.ngrok's free tier shows a "You are about to visit…" warning page that breaks the browser OAuth redirect (you can't inject thengrok-skip-browser-warningheader into Slack's redirect); if you must use ngrok free, open the tunnel URL once in your browser and click Visit Site first, or upgrade to a paid plan.For Slack, the API base the frontend calls and
SLACK_CALLBACK_URLmust be the same HTTPS origin — the anti-CSRF nonce cookie is set onoauth/startand read on the callback, so alocalhoststart + tunnel callback will not work.
1. Database Migration
The migration creates the required tables (tasks.messaging_connections, tasks.messaging_link_tokens, tasks.messaging_identity_map) automatically. The migration container handles this on startup — no manual steps needed.
2. Telegram
2.1 Create a bot
- Open @BotFather in Telegram
- Send
/newbotand follow the prompts - Copy the bot token (looks like
123456789:AA...) and note the bot username (without@)
2.2 Configure the bot for group commands
Project connections are completed by typing a command inside a group, so the bot must be allowed to read group messages:
- In @BotFather, send
/setprivacy→ choose your bot → Disable (with privacy enabled, members must instead address the bot explicitly:/connect@your_bot <token>) - Send
/setjoingroups→ choose your bot → Enable
2.3 Environment variables
Add to .env.taskview:
TELEGRAM_BOT_TOKEN=123456789:AA-your-bot-token
TELEGRAM_BOT_USERNAME=your_bot # without the leading @
TELEGRAM_WEBHOOK_SECRET=<random-secret> # any long random string
Generate a webhook secret:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
The webhook secret is compared (in constant time) against the
X-Telegram-Bot-Api-Secret-Tokenheader on every inbound update, so only Telegram can reach the endpoint.
2.4 Register the webhook with Telegram
Tell Telegram where to deliver updates. Run once (replace the placeholders):
curl "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/setWebhook" \
-d "url=https://<your-api-domain>/module/messaging/telegram/webhook" \
-d "secret_token=<TELEGRAM_WEBHOOK_SECRET>"
Verify it took effect:
curl "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/getWebhookInfo"
url should point at your /module/messaging/telegram/webhook and pending_update_count should be 0.
2.5 Usage
Restart the API after setting the environment variables, then in TaskView open a project → right-click it in the sidebar → "Messengers".
Personal (Direct messages)
- On the Personal tab, click Connect → Telegram → Get link
- Click Open in Telegram and press Start in the bot chat
- Your account is linked — you'll now get DMs about your tasks
Project (Group channel)
- On the Project tab (requires the
INTEGRATIONS_CAN_MANAGEpermission), click Connect → Telegram → Get link - Click Add bot to group and pick the group, or add the bot manually
- In that group, send the shown command:
/connect <token> - The group is connected — project events are now posted there
You can toggle any connection on/off or disconnect it from the same page.
Creating tasks from a group (/task)
In a group linked to exactly one project, any member can create a task:
/task Buy the domain
- Requires the sender to have linked their personal Telegram account (Personal tab → Connect → Telegram) — the command runs under their TaskView permissions (
COMPONENT_CAN_ADD_TASKS). - The bot replies with the created task and a link. In an unlinked chat, or one linked to several projects, it says so and creates nothing.
- Privacy mode note: with the bot's group privacy enabled, Telegram only delivers commands addressed to the bot — use
/task@your_bot <description>(same as/connect). Disable privacy (and re-add the bot) to allow a bare/task.
3. Slack
Slack uses per-instance OAuth credentials, mirroring the GitHub/GitLab integration model. Requires ENCRYPTION_KEY (tokens are stored AES-256-GCM encrypted).
Public HTTPS is required. Slack must reach your API over HTTPS for OAuth, slash commands and interactivity. In local dev use a tunnel (ngrok / cloudflared); note that a free ngrok URL changes on every restart — if it changes you must update the Redirect URL, the Slash Command / Interactivity Request URLs and
SLACK_CALLBACK_URL, then restart.
3.1 Create the app
- Go to api.slack.com/apps → Create New App → From scratch → name it (e.g.
TaskView) and pick your workspace. - App name vs bot name (these are two different fields — a common point of confusion):
- Basic Information → Display Information → App name — the app's name in the api.slack.com console (the dropdown at the top-left). Purely cosmetic for the console.
- App Home → App Display Name → Edit → Display Name (Bot Name) — how the bot signs its messages in Slack (e.g.
TaskView). Changing it only takes effect after a Reinstall (step 5).
3.2 OAuth & scopes
- OAuth & Permissions → Redirect URLs → Add New Redirect URL — enter exactly your
SLACK_CALLBACK_URL, then Save URLs:https://<your-api-domain>/module/messaging/slack/oauth/callback
Slack rejects plainhttp://localhost— HTTPS only. - OAuth & Permissions → Scopes → Bot Token Scopes — add:
chat:write— post messages (personal DMs + project channels). Required.commands— needed for the/taskslash command (inbound).chat:write.public— optional but recommended: lets the bot post to any public channel without being invited. Without it you must/invitethe bot into each channel (see step 5).im:write— optional, only if personal DMs fail to open.
incoming-webhookis added automatically when you enable Incoming Webhooks (step 3.3) and is what makes the project-connect channel picker appear.
3.3 Features
- Incoming Webhooks → Activate Incoming Webhooks: On. Required for the project (channel) connect flow — it makes Slack show a channel picker during install.
- Slash Commands → Create New Command:
- Command:
/task - Request URL:
https://<your-api-domain>/module/messaging/slack/commands - Short description:
Create a TaskView task
- Command:
- Interactivity & Shortcuts → On, Request URL:
https://<your-api-domain>/module/messaging/slack/interactivity
Powers the Done / Reopen / Assign buttons and the assignee modal.
3.4 Credentials → environment variables
Basic Information → App Credentials — copy the three values:
SLACK_CLIENT_ID=<your-client-id>
SLACK_CLIENT_SECRET=<your-client-secret>
SLACK_CALLBACK_URL=https://<your-api-domain>/module/messaging/slack/oauth/callback
SLACK_SIGNING_SECRET=<your-signing-secret>
SLACK_CALLBACK_URLmust exactly match the Redirect URL from step 3.2.SLACK_SIGNING_SECRETverifies inbound slash commands / interactivity (without it those endpoints return 503).- Tokens are stored AES-256-GCM encrypted —
ENCRYPTION_KEYis required. - Optional endpoint overrides (tests / enterprise proxy):
SLACK_API_BASE_URL,SLACK_AUTHORIZE_URL,SLACK_WEBHOOK_PREFIX(see the environment-variables reference).
Then restart the API.
3.5 Install (and reinstall)
- OAuth & Permissions → Install to Workspace (or Reinstall to Workspace).
- Reinstall whenever you change scopes or the bot display name — otherwise changes don't take effect.
- Invite the bot into the channel(s) it should post to:
/invite @TaskViewchat.postMessagefails withnot_in_channelif the bot isn't a member. Either invite it per channel, or add thechat:write.publicscope (step 3.2) so it can post to any public channel without an invite (private channels always need an invite).
3.6 Connect and use
Open Messengers in a project:
- Personal: Personal tab → Connect → Slack → Continue with Slack → authorize. The bot DMs you about your tasks. This also links your Slack account (required for inbound actions to run as you).
- Project: Project tab (needs
INTEGRATIONS_CAN_MANAGE) → Connect → Slack → Continue with Slack → pick the channel during install. Project events post to that channel. Invite the bot into that channel (step 3.5).
Gotcha — "added an integration to this channel" is NOT enough. After you pick a channel during install, Slack posts "added an integration to this channel: TaskView" there. That only adds the app's incoming webhook — it does not make the bot user a channel member, and TaskView posts via the bot (
chat.postMessage, so it can also render buttons). So delivery still fails withnot_in_channeluntil you explicitly run/invite @TaskViewin that channel (or usechat:write.public). Confirm the connection's channel is the one you meant — the#channelyou picked in the webhook dropdown is where events go.
Acting from Slack:
/task <description>in a channel linked to exactly one project → creates a task there. (In a DM or an unlinked channel it replies that the channel isn't linked; a channel linked to several projects is ambiguous and is refused — create it in the app.)- Under each task notification: ✅ Done (complete), ↩️ Reopen (on a completed card), 👤 Assign (a modal that pre-selects current assignees; add/remove and Save to set the list).
- Every inbound action runs under the invoking user's TaskView RBAC, so they must have linked their Slack account. Requests are verified with the Signing Secret.
Notification model: messengers are a feed/history — each event posts a new message. TaskView does not edit past cards, so an old card may still show a live button after the task changed elsewhere. Actions are idempotent and always re-check the current state + permissions, so a stale click is safe.
3.7 Distribution
By default a Slack app is private to your workspace. To let other workspaces install it, enable Public Distribution (free); to list it in the Slack Marketplace, pass Slack's review (also free — a review, not a fee). Self-hosted installs keep the app private; the official SaaS uses one distributed app. Creating and distributing an app does not require a paid Slack plan.
4. Full .env.taskview Example
# ... existing vars ...
# Telegram messaging integration
TELEGRAM_BOT_TOKEN=123456789:AA-your-bot-token
TELEGRAM_BOT_USERNAME=your_bot
TELEGRAM_WEBHOOK_SECRET=a1b2c3d4e5f6...
# Slack messaging integration
SLACK_CLIENT_ID=
SLACK_CLIENT_SECRET=
SLACK_CALLBACK_URL=https://api.taskview.tech/module/messaging/slack/oauth/callback
SLACK_SIGNING_SECRET=
# Required for Slack (encrypted token storage), shared with SSO/other integrations
ENCRYPTION_KEY=a1b2c3d4e5f6... # 64 hex characters
5. SaaS vs Self-Hosted
| Official SaaS | Self-hosted | |
|---|---|---|
| Telegram | One official bot, credentials shipped with the instance | Create your own bot via @BotFather and set TELEGRAM_* |
| Slack | One official distributed app | Create your own Slack app and set SLACK_* |
There is no code fork between the two — only the values of the environment variables differ. If the variables are unset, the integration simply does not appear until an admin configures it.
6. Production Notes
- Public HTTPS is mandatory for the inbound webhook. Update the
setWebhookURL to your production domain. - Re-run
setWebhookwhenever the API domain changes. TELEGRAM_WEBHOOK_SECRET: store securely, never commit to git.- Group privacy: if
/connectin a group does nothing, the bot's privacy mode is likely still enabled (see step 2.2).
7. Troubleshooting
The "Messengers" page shows a 503 / "This messenger is not configured on the server"
→ TELEGRAM_BOT_TOKEN or TELEGRAM_BOT_USERNAME is missing; the API was not restarted after setting them.
Pressing Start / sending /connect does nothing
→ The webhook is not registered or points at the wrong URL. Check getWebhookInfo (step 2.4) and confirm the API is reachable over public HTTPS.
/connect <token> in a group is ignored
→ The bot's privacy mode is enabled. Disable it (step 2.2), or use /connect@your_bot <token>.
"Link is invalid or expired" → Binding tokens are single-use and expire after 15 minutes. Generate a new one from the Connect dialog.
Notifications don't arrive after connecting → Confirm the connection is toggled on, and that you are actually assigned to the task. Project posts require an active project connection.
Slack
Nothing posts to a linked channel (personal DM works) — the log shows chat.postMessage failed: not_in_channel
→ The bot user isn't a member of that channel. Seeing "added an integration to this channel: TaskView" is not the same as the bot joining — that message is just the incoming webhook. Run /invite @TaskView (the bot user) in the channel, or add the chat:write.public scope and reinstall. The log line names the channel id (channel=…) — make sure it's the one you intended.
/task replies "This channel is not linked to a TaskView project"
→ Run it in a project channel (connected via Messengers → Project), not in a DM. A channel linked to several projects is refused as ambiguous — create the task in the app.
Slash command / buttons return an error or nothing
→ SLACK_SIGNING_SECRET is missing (endpoints return 503), the Request URLs don't match your current public host, or the app wasn't reinstalled after enabling commands / chat:write.
"You need to link your Slack account" → Inbound actions run under your TaskView permissions. Link it: Personal tab → Connect → Slack.
Bot still shows the old name in messages → Changing Display Name (Bot Name) requires Reinstall to Workspace. The console dropdown name is a separate field (Basic Information → App name).
MCP Server
Connect AI assistants like Claude Code and Claude Desktop to TaskView via the Model Context Protocol (MCP). Manage projects and tasks with a scoped API token.
Environment Variables
Complete reference for TaskView environment variables - database connection, JWT authentication, OAuth providers, SMTP email, GitHub/GitLab integration, encryption, and CORS configuration for your self-hosted Docker deployment.
