Overview
A survey can be distributed over several channels. Each channel is reached through a provider port โ an interface in @voc/ports with a swappable adapter โ so the domain logic never touches a vendor SDK directly. The orchestration that decides what to send lives in DispatchService (api/src/domains/dispatch.ts); the adapters decide how to send it.
Email is sent through the EmailProvider port, implemented by the Resend adapter (packages/adapters/src/resend.ts). Invitation orchestration is the email branch of DispatchService.invite() (api/src/domains/dispatch.ts).
SMS & IVR
SMS and IVR share a single turn-by-turn conversation engine. Outbound origination goes through the Twilio adapter (packages/adapters/src/twilio.ts); inbound turns arrive on the public Twilio webhooks (api/src/routes/twilio.ts) and are driven by ChannelConversationService (api/src/domains/channel-conversation.ts).
Short URLs
Short URLs keep distribution links compact โ particularly for SMS, where a full survey URL with ?agent= and cd. query data would be unwieldy. Unlike email/SMS/IVR, this is not* a provider port: it is a tenant database service plus a root-mounted public redirect.