Telegram & Slack Setup

Connect Telegram and Slack to TaskView to receive task notifications. Personal direct messages and project-wide group channels, with instance-level bot credentials, OAuth, signed inbound webhooks, and SHA-256 hashed binding tokens.

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.

EventPersonal recipients
Task createdall project members (a new task usually has no assignees yet)
Task assignedthe task's assignees
Task status changedthe task's assignees
Task completed / reopenedthe task's assignees
Sprint / member / time-tracking eventsthe 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_CONTENT and the assignee footer by TASKS_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; localhost is not reachable from Telegram/Slack — use your production domain or a tunnel for local testing)
  • PostgreSQL database with migrations applied
  • .env.taskview file 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 the ngrok-skip-browser-warning header 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_URL must be the same HTTPS origin — the anti-CSRF nonce cookie is set on oauth/start and read on the callback, so a localhost start + 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

  1. Open @BotFather in Telegram
  2. Send /newbot and follow the prompts
  3. 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:

  1. In @BotFather, send /setprivacy → choose your bot → Disable (with privacy enabled, members must instead address the bot explicitly: /connect@your_bot <token>)
  2. 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-Token header 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)

  1. On the Personal tab, click ConnectTelegramGet link
  2. Click Open in Telegram and press Start in the bot chat
  3. Your account is linked — you'll now get DMs about your tasks

Project (Group channel)

  1. On the Project tab (requires the INTEGRATIONS_CAN_MANAGE permission), click ConnectTelegramGet link
  2. Click Add bot to group and pick the group, or add the bot manually
  3. In that group, send the shown command: /connect <token>
  4. 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

  1. Go to api.slack.com/appsCreate New AppFrom scratch → name it (e.g. TaskView) and pick your workspace.
  2. 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

  1. 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 plain http://localhost — HTTPS only.
  2. OAuth & Permissions → Scopes → Bot Token Scopes — add:
    • chat:write — post messages (personal DMs + project channels). Required.
    • commands — needed for the /task slash command (inbound).
    • chat:write.publicoptional but recommended: lets the bot post to any public channel without being invited. Without it you must /invite the bot into each channel (see step 5).
    • im:writeoptional, only if personal DMs fail to open.

    incoming-webhook is added automatically when you enable Incoming Webhooks (step 3.3) and is what makes the project-connect channel picker appear.

3.3 Features

  1. Incoming Webhooks → Activate Incoming Webhooks: On. Required for the project (channel) connect flow — it makes Slack show a channel picker during install.
  2. Slash Commands → Create New Command:
    • Command: /task
    • Request URL: https://<your-api-domain>/module/messaging/slack/commands
    • Short description: Create a TaskView task
  3. 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_URL must exactly match the Redirect URL from step 3.2.
  • SLACK_SIGNING_SECRET verifies inbound slash commands / interactivity (without it those endpoints return 503).
  • Tokens are stored AES-256-GCM encrypted — ENCRYPTION_KEY is 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)

  1. OAuth & Permissions → Install to Workspace (or Reinstall to Workspace).
  2. Reinstall whenever you change scopes or the bot display name — otherwise changes don't take effect.
  3. Invite the bot into the channel(s) it should post to:
    /invite @TaskView
    
    chat.postMessage fails with not_in_channel if the bot isn't a member. Either invite it per channel, or add the chat:write.public scope (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 → ConnectSlackContinue 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) → ConnectSlackContinue 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 with not_in_channel until you explicitly run /invite @TaskView in that channel (or use chat:write.public). Confirm the connection's channel is the one you meant — the #channel you 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 SaaSSelf-hosted
TelegramOne official bot, credentials shipped with the instanceCreate your own bot via @BotFather and set TELEGRAM_*
SlackOne official distributed appCreate 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 setWebhook URL to your production domain.
  • Re-run setWebhook whenever the API domain changes.
  • TELEGRAM_WEBHOOK_SECRET: store securely, never commit to git.
  • Group privacy: if /connect in 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 nothingSLACK_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).

Built with Nuxt UI • © 2026 Thank you Nuxt Team for this awesome UI library and for the template!

Built solo by Nikolai Giman