Slack helpdesk/offboarding module for common mailbox and Google Workspace tasks across many Workspace tenants. The clean Slack modal UI can be served by the Cloud Run backend, while the Apps Script file remains as the original prototype and fallback implementation.
For production, use backend/. It verifies Slack request signatures, routes by
email domain, dispatches work through Cloud Tasks, impersonates the correct
Google Workspace tenant, and DMs results back to the requester.
backend/: FastAPI Cloud Run backend for the production Slack module.backend/main.py: Slack endpoints, modal builder, domain router, Cloud Tasks
dispatch, Google Workspace execution, and Okta Workflows fallback.offboard_tasks.js: Apps Script prototype/fallback source for the Slack slash
command, modal, routing, Google Workspace execution, and Okta Workflows
fallback.See backend/README.md for Cloud Run deployment, route config, and tenant
onboarding. For the 35+ Workspace rollout checklist, see
backend/TENANT_ROLLOUT.md. For current project state and chat-to-chat
continuation notes, see HANDOFF.md.
These apply only to offboard_tasks.js. The production Cloud Run backend uses
environment variables documented in backend/README.md.
SLACK_BOT_TOKEN: Slack bot token used for opening modals and sending DMs.SLACK_REQUEST_SECRET: shared secret for direct Apps Script deployments. Add
the same value to the Slack slash command and interactivity URLs as
?slack_secret=....SLACK_VERIFICATION_TOKEN: optional legacy Slack verification token check.
Prefer SLACK_REQUEST_SECRET or a signature-verifying proxy for new work.SLACK_ALLOWED_TEAM_IDS: optional comma/newline-separated Slack workspace IDs.SLACK_ALLOWED_API_APP_IDS: optional comma/newline-separated Slack app IDs.SLACK_ALLOWED_USER_IDS: optional comma/newline-separated helpdesk user IDs.SLACK_ALLOWED_CHANNEL_IDS: optional comma/newline-separated channel IDs.SLACK_SIGNING_SECRET: Slack signing secret. Direct Apps Script web apps do
not expose the inbound Slack signature headers to doPost(e), so use this
with a Cloud Run/Cloud Functions/proxy front end if you need full Slack
request-signature verification.OFFBOARD_EXECUTION_MODE: auto, google, or workflow. auto uses
Google Workspace when GW_ROUTES_JSON is configured, otherwise it keeps the
existing Okta Workflows route.GW_SERVICE_ACCOUNT_JSON: shared service account JSON. You can also use
GW_SERVICE_ACCOUNT_EMAIL and GW_PRIVATE_KEY instead.GW_ROUTES_JSON: domain-to-tenant routing config. If this gets too large for
one Apps Script property, split it into GW_ROUTES_JSON_1,
GW_ROUTES_JSON_2, etc.OW_ROUTER_URL: default Okta Workflows endpoint when using workflow mode.OW_BEARER_TOKEN: bearer token for the Okta Workflows endpoint.Routes can be stored as an array:
[
{
"label": "Example Workspace",
"domains": ["example.com", "example.org"],
"adminEmail": "workspace-admin@example.com",
"customerId": "my_customer",
"gcpProjectId": "example-gcp-project"
}
]
Or as a compact object keyed by domain:
{
"example.com": {
"label": "Example Workspace",
"adminEmail": "workspace-admin@example.com"
}
}
For route-specific service accounts, add "serviceAccountJsonProperty":
"GW_SA_EXAMPLE_JSON" to a route and store that JSON in a separate Apps Script
property.
Authorize the service account client ID for domain-wide delegation in each Google Workspace tenant with these scopes:
https://www.googleapis.com/auth/gmail.settings.basichttps://www.googleapis.com/auth/gmail.settings.sharinghttps://www.googleapis.com/auth/admin.directory.group.member.readonlyhttps://www.googleapis.com/auth/admin.directory.user.readonlyGmail settings calls impersonate the mailbox user being changed. Admin
Directory calls, such as Google Group member lookups, impersonate the route’s
adminEmail.
The production Cloud Run backend protects every route’s adminEmail
automatically and checks Google Directory isAdmin in strict mode before every
mailbox action. Add break-glass and other sensitive addresses to the route’s
protectedEmails array. Protected addresses are rejected in Slack and again by
the worker before Google API execution. See backend/README.md for the route
format and preflight test.
For a direct Apps Script web app, set a long random SLACK_REQUEST_SECRET and
append it to both Slack request URLs:
https://script.google.com/macros/s/.../exec?slack_secret=LONG_RANDOM_VALUE
Also set SLACK_ALLOWED_TEAM_IDS, SLACK_ALLOWED_API_APP_IDS, and
SLACK_ALLOWED_USER_IDS before giving this to helpdesk. The allowlists are not
a replacement for signed Slack requests, but they are useful blast-radius
controls and catch accidental cross-workspace/app use.
For strongest production posture, put a small HTTPS service in front of Apps
Script that verifies Slack’s X-Slack-Signature and
X-Slack-Request-Timestamp headers, then forwards only verified requests to
Apps Script. Keep the Apps Script URL secret and still use SLACK_REQUEST_SECRET
between the proxy and Apps Script.
Slack requires slash commands to be acknowledged within 3 seconds. The slash command path does that now, but modal submissions run the Google action before returning. If group lookups or bundled actions start getting slow, move the execution step to a queue/worker and immediately DM the requester that the task has been accepted.
Run this from the repo root before redeploying the Apps Script source:
node --check integrations/slack_helpdesk_module/offboard_tasks.js
Run this before deploying the Cloud Run backend:
python -m py_compile integrations/slack_helpdesk_module/backend/main.py