Public Ticket Portal

The Public Ticket Portal gives each customer a unique, secure URL where they can view their support conversation in a browser and post a reply β€” without needing email at all.

When enabled, every outbound email gets a small β€œView this conversation” footer linking to a per-customer, per-ticket URL like:

https://api.scitor.io/portal/9c0a…b3f

Customers don’t need an account. The token is the credential β€” long, random, and unguessable.

Why use it

  • Mobile-friendly replies. Customers can answer from a browser without hunting through their inbox.
  • Lost emails are no problem. If the original message was deleted or filtered to spam, the link still works.
  • CAPTCHA-protected. Replies are gated by Cloudflare Turnstile to keep bots out.
  • Auto-expires after close. Once an issue is closed, the link remains valid for a configurable retention window, then returns 410 Gone.

How it works

  1. Outbound email. When /send, /sendall, or /reply produces an email, Scitor finds-or-creates a portal ticket for that (issue, customer email) pair.
  2. Footer added. A short footer with the portal URL is appended to both the HTML and plain-text bodies of the outbound email.
  3. Customer clicks the link. They see the public timeline of inbound + outbound messages and a reply form (if allow_reply is on).
  4. Customer replies. The reply is posted as a comment on the GitHub issue / discussion, attributed as πŸ“¨ Reply via portal β€” from name <email>, and recorded on the timeline.
  5. Issue closes. The retention timer starts. After it elapses the page returns 410 Gone.
  6. Issue reopens. The expiry is cleared and the link works again.

Configuration

Add a portal: block to .github/scitor.yaml:

portal:
  enabled: true                 # Required to opt in. Default: false.
  allow_reply: true             # Allow customers to reply via the portal. Default: true.
  retention_after_close: 30d    # How long the link stays valid after the issue closes. Default: 30d.

Options

Option Type Default Description
enabled boolean false Master switch. The feature is fully off when not set to true.
allow_reply boolean true If false, the timeline is read-only.
retention_after_close duration 30d Time the link remains valid after issues.closed. Accepts 30m, 2h, 7d, 4w.

Spam protection (CAPTCHA) on the reply form is built-in and managed by Scitor β€” no setup required.

Privacy & retention

  • Tokens are unguessable β€” no enumeration possible.
  • Per-ticket scope. A token only ever exposes the messages on a single ticket for a single customer.
  • No internal comments. The timeline contains only the messages the customer themselves sent or received via email; team-only GitHub comments are never shown.
  • Replies are sanitized. HTML pasted into the reply box is stripped before being stored or posted to GitHub.
  • Hard expiry. After retention_after_close elapses post-close, the page returns 410 Gone and the timeline is no longer accessible.
  • Revocation. A ticket can be revoked at any time by an admin; revoked links return 404.

Security

  • Tokens are stored as the primary key β€” no separate signature is needed because the token itself has 256 bits of entropy.
  • Replies require a Cloudflare Turnstile challenge. If TURNSTILE_SECRET_KEY isn’t configured on the worker, replies are accepted with a warning logged.
  • Reply bodies are capped at 10,000 characters.

Limitations (v1)

  • Attachments are not yet supported on portal replies.
  • The portal is served from the API worker (api.scitor.io/portal/:token); a custom-domain version may come later.
  • Inbound replies that arrive via email after the original ticket is created aren’t yet mirrored onto the portal timeline (the initial email and all outbound replies are).

Was this article helpful?

Scitor β€” Turn GitHub into your support platform