Skip to content

feat(sbat): inbound email webhook for support tickets#76

Open
ejfox wants to merge 5 commits intomainfrom
feat/sbat-inbound-email-webhook
Open

feat(sbat): inbound email webhook for support tickets#76
ejfox wants to merge 5 commits intomainfrom
feat/sbat-inbound-email-webhook

Conversation

@ejfox
Copy link
Copy Markdown
Contributor

@ejfox ejfox commented Mar 12, 2026

Summary

  • Adds /api/sbat/inbound-email webhook endpoint for receiving MailerSend inbound emails as support tickets
  • New support_tickets DB table with auto-incrementing ticket numbers (SBAT-0001, etc.)
  • Tickets posted to #subwaybuilder-sbat Discord channel with priority emoji and formatted preview
  • CRUD endpoints for listing, viewing, and updating tickets

Endpoints

Method Path Description
POST /api/sbat/inbound-email MailerSend webhook receiver
GET /api/sbat/tickets List tickets (optional ?status=open)
GET /api/sbat/tickets/:number Get single ticket
PATCH /api/sbat/tickets/:number Update status/assignee/priority

Files changed

  • packages/capabilities/src/routes/sbat.ts — new route file (296 lines)
  • packages/capabilities/src/index.ts — mount route at /api/sbat
  • packages/shared/src/db/schema.tssupport_tickets table definition

Test plan

  • POST test email → ticket created in DB, posted to Discord
  • GET /tickets returns list
  • TypeScript compiles clean
  • Configure SBAT_WEBHOOK_SECRET env var when MailerSend is set up
  • Point MailerSend inbound route to https://capabilities.tools.ejfox.com/api/sbat/inbound-email

🤖 Generated with Claude Code

ejfox and others added 5 commits March 12, 2026 12:37
Add MailerSend-compatible webhook endpoint at /api/sbat/inbound-email
that receives inbound emails and creates support tickets. Tickets are
stored in a new support_tickets table and posted to #subwaybuilder-sbat
Discord channel for team visibility.

Endpoints:
- POST /api/sbat/inbound-email — receive MailerSend webhook
- GET /api/sbat/tickets — list tickets (filterable by status)
- GET /api/sbat/tickets/:number — get single ticket
- PATCH /api/sbat/tickets/:number — update status/assignee/priority

Features:
- Auto-generates ticket numbers (SBAT-0001, SBAT-0002, ...)
- Auto-detects priority from subject/body keywords
- Verifies MailerSend webhook signature (SBAT_WEBHOOK_SECRET)
- Posts formatted ticket summary to Discord channel
- Supports both MailerSend and generic JSON payload formats

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Require SBAT_WEBHOOK_SECRET (reject if unconfigured)
- Fix timingSafeEqual crash on length-mismatched signatures
- Prefer raw body for signature verification over re-serialized JSON
- Add rate limiter (10 requests/minute) on inbound endpoint
- Validate status/priority enums on PATCH (whitelist only)
- Validate ticket number format (SBAT-NNNN) on GET/PATCH
- Validate sender email format on inbound
- Truncate inputs (subject 500, body 50k, name 200, assignee 100)
- Strip raw payload from stored metadata (no PII echo)
- Remove error details from 500 responses (no internal leaks)
- Exclude body_text/body_html from list endpoint (summary only)
- Log structured update fields instead of raw req.body

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
express.json() discards the raw buffer by default, so rawBody was
always undefined and signature verification fell back to re-serialized
JSON (which may not match what MailerSend signed). Add verify callback
to capture the original buffer when the MailerSend signature header
is present.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SBAT has its own app and DB on Vercel. Coach Artie is just the relay
between MailerSend and the Subway Builder Discord channels.

Removed:
- support_tickets DB table and schema
- Ticket CRUD endpoints (GET/PATCH /tickets)
- Ticket number generation
- getSyncDb import

Kept:
- POST /api/sbat/inbound-email — validates + relays to Discord
- Signature verification, rate limiting, priority detection

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant