Asks: Multichannel Intake Guide

Asks lets anyone file a request into an Ithura project from Slack, email, or a public web form, without an Ithura account. Every request becomes a normal intake issue (triage state, same activity feed, same comment thread), and updates flow back to the submitter on the channel they came from.

ChannelInboundOutbound (back to submitter)
SlackEmoji reaction or @bot mention files the message as an issue; thread replies become issue commentsConfirmation + replies to the submitter post into the same Slack thread
EmailMail to <local-part>@<inbound-domain> creates an issue; replies append as commentsConfirmation + replies to the submitter arrive as email replies in the same thread
Web formPublic form submission creates an issueConfirmation email + a tokenised, no-login status page

Only updates the team explicitly shares reach the submitter. See Privacy: what the submitter sees below.

How it works

Every intake request gets an intake_threads row: the channel, a channel-native thread anchor (external_ref), the submitter's email/display name, and an opaque reply_token. That row is what routes replies in both directions. Submitters are never given accounts; issues and comments they produce carry NULL author fields plus an inline attribution label.

Comments arriving FROM a channel are inserted directly and never re-relayed, so there are no echo loops. Slack bot posts are likewise ignored on the inbound side.

Privacy: what the submitter sees

Comments have an access level: INTERNAL (the default) or EXTERNAL. This is the boundary between private triage and what reaches the person who filed the request.

  • Internal by default. Every ordinary comment is INTERNAL. Internal triage discussion stays inside Ithura. It is never emailed, never posted to the Slack thread, and never shown on the public status page.
  • External updates are opt-in. Only a comment explicitly marked EXTERNAL is relayed back to the submitter (as a Slack thread reply and/or an email reply) and listed on their status page.

Triagers post an external update from the inbox detail using the Reply to submitter box (see Triage: replying to the submitter). The confirmation message the submitter first receives is always sent; it is the only automatic outbound message.

Triage: replying to the submitter

Open a request in the project inbox to triage it. The detail panel gives you everything you need to answer the submitter without leaving Ithura.

  • Reply to submitter box: type an update and post it. This creates an EXTERNAL comment, so it is the one kind of comment that goes back out. The box states where the update will land (emailed to the address on file and/or posted to the Slack thread, plus the status page). Regular comments elsewhere on the issue remain internal.
  • From: for email and form requests that captured an address, the submitter email shows here as a mailto: link.
  • View in <channel>: Slack-sourced requests show a clickable button that opens the original Slack thread (see Provenance).
  • Attachments: files that arrived with the request render with their real name and size, and are downloadable.

The same View in Slack provenance button also appears on the full issue detail panel, not just the inbox.

Provenance: tracing a request to its source

Slack-sourced issues expose a source_url that deep-links to the original Slack thread. It is built from the captured channel:thread_ts anchor stored on the intake thread, so triagers can jump straight to the conversation the request came from. The link renders as a View in Slack button in both the inbox detail and the issue detail panel. Email and form requests have no such link.

Attachments

Attachments from any channel (email parts, Slack files relayed on capture, and web-form _attachments) are stored as normal issue attachments with their real filename, content type, and byte size (each capped at 5 MB). They appear in the inbox detail's attachment list with a download action, so a triager sees exactly what the submitter sent.


1. Slack

Asks intake rides on the same Slack integration as channel sync. Once Slack is enabled for your instance and connected to your workspace (see the Slack guide), turn on intake per project below.

Project setup

Project settings → Integrations → Slack: pick the channel (existing outbound sync), then flip on Asks intake and optionally change the trigger emoji (default :ticket:). Intake maps that channel to this project.

Using it

  • React with the trigger emoji on any message in the synced channel: the message becomes an intake issue (first line = title, full text = description). The bot replies in the thread with an issue link and an "Edit details" button (title + priority modal). If the underlying issue has since been deleted, clicking "Edit details" opens a short "Request unavailable" modal instead of silently doing nothing.
  • @mention the bot with a request: same, using the mention text. Mentioning the bot inside an already-captured thread adds a comment instead.
  • Thread replies into Ithura: Slack replies in a captured thread become issue comments labelled "Reply from <name> (Slack)".
  • Replies back to Slack: only an update the team explicitly shares (an EXTERNAL comment, posted from the inbox "Reply to submitter" box) is posted into the thread as *<name>*: <text>. Internal comments stay in Ithura.

A second reaction on the same message never opens a duplicate: the thread anchor is unique per message.


2. Email

Once inbound email is enabled for your instance, each project gets its own intake address that turns mail into issues.

Project setup

Project settings → Email-in: create an address. Auto-generated as intake-<id>@<domain>, or pick a custom local part (support-payments@<domain>). Each address targets one project (and optionally a specific intake).

Using it

  • Send or forward mail to the address: subject → title, body → description, attachments (up to 5 MB each) are stored on the issue with their real filename and size.
  • The submitter gets a confirmation email. Its Message-ID encodes the issue, so any reply (even months later, even with a truncated References chain) lands as a comment on the right issue. Reply matching runs three passes: our synthesised Message-IDs, the thread's recorded notification ID, then any previously seen inbound Message-ID.
  • Updates the team shares (EXTERNAL comments, posted from the inbox "Reply to submitter" box) are emailed to the submitter as replies in the same thread ("<name> replied: …", plus the status link). Internal comments are not emailed.
  • Duplicate webhook deliveries dedupe on (address, Message-ID).

3. Web form

Project setup

Project settings → Forms: create a form with a slug, title and fields (text, textarea, email, number, single_select, multi_select, checkbox, url, date). The public page is served at:

https://<web-host>/forms/<workspace-slug>/<form-slug>

Embed anywhere with one line:

<iframe src="https://<web-host>/forms/<workspace-slug>/<form-slug>" width="100%" height="720" style="border:0" title="Submit a request" loading="lazy"></iframe>

Using it

  • Submissions create an intake issue (title/description mapped from conventional field keys, full payload preserved on the intake record).
  • If the form has an email-typed field and the visitor fills it, they get a confirmation email with a "View your request" link: https://<web-host>/asks/status/<reply-token> (a read-only, no-login status page). The link is also shown on the form's thank-you screen.
  • Optional attachments: POST a _attachments array of {filename, content_type, data(base64)} (5 MB per file cap). Each is stored on the issue with its filename and size.
  • Abuse guards: honeypot field, 10 submissions/IP/hour.

The status page

The no-login status page (/asks/status/<reply-token>) is keyed on the opaque reply token, so it works for any intake thread that carries one, not just forms. It shows:

  • the request title and current state (with a colour dot for progress),
  • What you sent: the submitter's own submitted message,
  • Updates from the team: a timeline of EXTERNAL comments only, each with author and timestamp,
  • submitted and last-updated timestamps.

Internal triage comments never appear here. The page has no authentication and can be revisited anytime.


Endpoint reference

EndpointAuthPurpose
POST /webhooks/slack/events/Slack signing secretEvents API ingress
POST /webhooks/slack/interactivity/Slack signing secretEdit-details modal
POST /webhooks/email-in/X-Inbound-TokenInbound email ingress
GET /public/forms/{ws}/{form}/noneForm schema
POST /public/forms/{ws}/{form}/submit/none (rate-limited)Form submission
GET /public/asks/status/{token}/reply tokenSubmitter status page
GET/POST/DELETE …/slack/sync/project adminChannel + intake toggle
GET/POST/PATCH/DELETE …/email-in/project memberInbound addresses
…/forms/ CRUDworkspace adminForm management

Smoke verification checklist

  1. React with the trigger emoji on a Slack message → issue appears in the project within ~5 s; threaded confirmation with issue link visible.
  2. Post an update from the inbox "Reply to submitter" box (an EXTERNAL comment) → it appears as a Slack thread reply. A plain internal comment does not.
  3. Email with two attachments to the intake address → issue created, attachments listed on the issue.
  4. Reply to the confirmation email → reply appears as an issue comment.
  5. Submit the public form with an email → issue created; confirmation email contains a status URL that opens without login.
  6. Confirm none of the three submitters exist as workspace members.

Automated coverage: internal/handlers/asks_intake_test.go (email reply threading, form thread + status page, Slack signature gate, full reaction→issue→reply flow against a fake Slack API) and asks_intake_unit_test.go.