WorkestraDocs
ModulesField Service

Email Intake

Capture customer inquiries from email — `service@…` alias creates an FSM Request automatically.

Field Service ships with an email intake processor that watches for inbound mail addressed to your workspace's intake alias. Every matching email automatically creates an FSM Request — your dispatcher then triages it like any other.

How it works

Customer emails service@workestra.app  (or service+yoursubteam@…)

    ▼ (inbound webhook → processor matches alias)
fsm_requests row created
    │  • source = "email"
    │  • summary = email subject (truncated to 200 chars)
    │  • description = best of body text / HTML body
    │  • dedup by Message-ID via synced_emails

Appears at /fsm/requests in status `new`

The processor lives at fsm-request-processor.ts and is registered with the shared processor-registry at module load.

Alias pattern

The processor matches two recipient patterns (case-insensitive):

PatternUse for
service@yourdomain.tldThe default workspace intake
service+<anything>@yourdomain.tldMulti-team setups — e.g. service+northregion@…, service+enterprise@…

Any other recipient (e.g. sales@…, support@…) is handed to the next registered processor — FSM intake doesn't claim mail it doesn't recognize.

Workspace-configurable intake aliases (e.g. bookings@…, repairs@…) and per-alias routing rules are queued for the Settings UI in Phase 4.8. For now, every workspace shares the service@ pattern.

What lands on the Request

FieldPopulated from
sourceemail
source_inbound_item_idToday: null (the email path uses synced_emails Message-ID dedupe, not inbound_items); will tighten when the inbound substrate migration lands remote
summaryEmail subject — (no subject) if missing
descriptionPlain-text body if present; else HTML-stripped to text
contact_id / company_idToday: null — auto-matching by sender email queued
service_type / preferred_window / location_*Today: null — the customer can include them in the email body but they're not parsed

The dispatcher fills in the structured fields during triage and links the customer.

Dedup

The processor dedupes on the inbound email's Message-ID (RFC 5322 header), captured in synced_emails.internet_message_id. Replaying the same webhook payload doesn't create duplicate requests.

What's NOT auto-parsed (yet)

The intake processor is intentionally lean today. It does not:

  • Parse a structured intake form from the email body
  • Auto-match the sender email to a CRM contact (queued)
  • Detect attachments and surface them on the request (the synced email keeps them, but they're not promoted to the request page)
  • Apply routing rules to assign by region / service type (queued for Phase 4.8 settings UI)

If you want richer intake today, point your web form at the channels framework instead and emit a routing rule with action create_fsm_request — that path supports custom field mapping.

Channel intake (WhatsApp / SMS / custom)

For non-email channels, the same flow runs through the create_fsm_request action handler registered on lib/channels/inbound-dispatcher. Configure a workspace routing rule:

when channel = whatsapp
  and to_handle = +1XXXXXXXXXX (your FSM intake number)
do create_fsm_request

Each matched message creates an fsm_requests row with source = "channel". The original message lives on channel_messages for click-through audit.

Replies + threading

The intake processor only creates new requests. Replies to an existing thread (same In-Reply-To / References chain) are stored in synced_emails but don't create a new request and don't auto-thread onto an existing one. Threading customer back-and-forth onto an in-progress request is queued.

If your dispatcher needs to follow up by email, use the linked Contact's email + your normal mail client. The Email Composer integration (with EmailModule = "FSM") is wired but not yet surfaced in the Requests UI.

Troubleshooting

No requests are showing up:

  1. Check the inbound webhook is firing — Settings → Integrations → Inbound mail logs.
  2. Confirm the recipient matches service@… or service+something@… (case-insensitive).
  3. Confirm the message has a Message-ID header — without one, the processor skips it.

A request appears with (no subject): The sending client didn't include a Subject header. Edit the request's summary manually during triage.

Same email created two requests: Shouldn't happen — open a bug. The Message-ID dedup is the safeguard; check whether the same email is arriving under different Message-IDs (some mailers rewrite on forward).

  • Requests — what these emails become
  • Workspace settings → Inbound mail for webhook config (platform docs)