Carriers, MessagingApp, Nooks and JustCall
Carrier registry
All carrier settings live in one place, EXTERNAL_PROVIDER_CONFIGS in lib/constants.ts:
export interface ExternalProviderConfig {
label: string // shown in the UI
source: string // MessagingApp `source` value on purchase/release
trunkSid: string // SIP trunk identifier
terminationUrl: string // Retell SIP termination URL used on import
}
EXTERNAL_PROVIDER_CONFIGS = {
twilio: { label: 'Twilio', source: 'twilio', trunkSid: NEXT_TWILIO_TRUNK_SID, terminationUrl: NEXT_TWILIO_TERMINATION_URL },
plivo: { label: 'Plivo', source: 'plivo', trunkSid: NEXT_PLIVO_TRUNK_SID, terminationUrl: NEXT_PLIVO_TERMINATION_URL },
}
EXTERNAL_PROVIDER_KEYS orders them for dropdowns; DEFAULT_EXTERNAL_PROVIDER is twilio. Individual
exports (TWILIO_TRUNK_SID, PLIVO_TERMINATION_URL, …) exist for backward compatibility.
Adding a carrier is a single new entry — PhoneNumberCallingService, MessagingAppPhoneService, the
purchase route and the Phone Numbers page all read the registry dynamically.
NEXT_TWILIO_TRUNK_SID=
NEXT_TWILIO_TERMINATION_URL=
NEXT_PLIVO_TRUNK_SID=
NEXT_PLIVO_TERMINATION_URL= # defaults to 14928144285976266.zt.plivo.com
MessagingApp
An internal Payability microservice that owns carrier accounts. The application never calls Twilio or Plivo directly — it asks MessagingApp to buy and release numbers, then imports them into Retell over SIP.
| Concern | File |
|---|---|
Base service with retry helper |
|
MessagingApp adapter |
|
Types |
|
NEXT_MESSAGING_APP_API_URL=
NEXT_MESSAGING_APP_API_KEY=
PHONE_NUMBER_DEFAULT_COUNTRY=US
MessagingAppPhoneService extends BasePhoneNumberService:
| Method | Purpose |
|---|---|
|
Buy a number for a state or area code from the given provider |
|
Return it to the carrier |
|
Carrier metadata, falling back to deriving it from the number itself |
Both write paths go through withRetry, because a transient failure while buying or releasing a number
would otherwise leak carrier spend.
`getPhoneNumberInfo’s fallback to number-derived data matters for the dialer: state extraction must work even when the metadata service is unavailable, or the selection algorithm has nothing to match on.
Acquisition flow
-
POST /api/phone-numberswith a state or area code and a provider. -
MessagingApp buys the number and returns its
phoneNumberSid. -
RetellClient.importNumber(phoneNumber, terminationUrl, outboundAgentId)imports it over the provider’s SIP trunk. -
The
PhoneNumbersrow is written with bothphoneNumberSidandretellPhoneNumberId.
Release is the reverse: unregister at Retell, release at MessagingApp, then mark isReleased with
releasedAt. The row is kept so historical calls keep a resolvable phoneNumberId.
Automatic purchasing during dialing is currently disabled — see Analysis, purchase and utility workflows.
Nooks
Nooks is the dialer human reps use. Only inbound webhooks are involved; the application does not call the Nooks API for call data.
NOOKS_WEBHOOK_SECRET= # optional; enables Bearer / X-Nooks-Signature verification
NOOKS_API_KEY= # present in the environment; not used by the webhook path
Calls arrive at POST /api/webhooks/nooks as call.logged and are stored with
channelType = "nooks_call", campaignId = null and targetId = null. Attribution is by
SalesRep.nooksUserId with an email fallback. Two disposition-gated flows follow: AI analysis, and a
Crm.DealUpdate closed-lost publish. See Nooks events.
FollowUpTask.nooksTaskId and nooksSequenceStateId exist for tasks and sequence enrollments mirrored
into Nooks, and SalesRep.nooksUserId is what makes that assignment possible.
Five9
POST /api/webhooks/five9 accepts a payload, stores it as a WebhookEvent, and returns. There is no
handler and no dispatch.
|
The route writes with |
JustCall (legacy)
JustCall predates the AI dialer and survives in three places:
-
Profile.justcallUserId,isAvailable,activeCalls,maxConcurrentCalls— the human agent availability model, andGET|PATCH /api/agents. -
Call.justCallIdon historical rows. -
The
handle_justcall_call_initiated()database trigger ontrg_webhookevent_call_initiated, which watches for JustCallcall.initiatedrows inWebhookEvent, extracts the JustCall user id, and marks the matching profile unavailable while incrementing its active-call count.
JustCallEventTypeEnum in lib/enums.ts enumerates the event names
(justcall_call.initiated, .answered, .incoming, .completed, .updated).
Availability tracking happens entirely in the trigger, with no application handler. New work should not
extend this path — current rep identity lives on SalesRep.