Skip to content

Phone Restrictions

Members can only place calls and send messages from phone numbers assigned to them. Organization owners bypass all restrictions and can use any phone. Members with no assignments cannot send messages — the compose bar is hidden until an admin assigns them a phone.

The resolve_member_allowed_phones service determines which phones a member can use:

src/mods/settings/services/resolve_member_phones_service.rs
pub async fn resolve_member_allowed_phones(
member_id: Uuid,
organization_id: Uuid,
is_owner: bool,
) -> Result<Vec<phone_number::Model>, AppError>

Three resolution paths:

Member typeResult
OwnerAll org phones — no restriction checks
Member with assignmentsOnly assigned phones from member_phone_number table
Member without assignmentsEmpty list — no phones available

Returns the current member’s allowed phones with default designation.

Response: Vec<MemberPhoneSummary>

[
{
"phone_number_id": "123e4567-e89b-12d3-a456-426614174000",
"number": "+15551234567",
"friendly_name": "Main Line",
"is_default": true
},
{
"phone_number_id": "223e4567-e89b-12d3-a456-426614174000",
"number": "+15559876543",
"friendly_name": "Support Line",
"is_default": false
}
]

Permission: Authenticated session (any role).

Both the call and messaging endpoints validate the selected phone against the allowed list before proceeding.

In initiate_call_api, the server resolves allowed phones and validates:

let allowed = resolve_member_allowed_phones(
session.member_id, org_id, session.is_owner
).await?;
// If phone_number_id provided, validate it's in the allowed list
// If not provided, auto-select from allowed list

When no phone is specified, the server auto-selects — preferring a phone with voice_webhook_active = true.

In send_message_api, the server checks the outbound phone:

let allowed_phones = resolve_member_allowed_phones(
session.member_id, org_id, session.is_owner
).await?;
if !allowed_phones.iter().any(|p| p.id == phone_number_id) {
return Err(HttpError::new(
StatusCode::FORBIDDEN,
"You are not assigned to this phone number",
));
}

Attempting to send from an unassigned phone returns 403 Forbidden.

Both the dial pad and message composer show a “From:” phone selector populated by GET /api/member/allowed-phones.

The DialPadPanel fetches allowed phones on mount and renders a SearchableSelect when multiple phones are available:

  • Multiple phones — searchable dropdown with “From:” label
  • Single phone — phone name displayed inline (no dropdown)
  • Default selectionis_default phone, or the first available

The MessageCompose component receives org_phones as a prop and applies default selection:

  1. Prefer the member’s default phone (is_default = true)
  2. Then the first available phone

If the allowed phones list is empty (non-owner with no assignments), the compose bar does not render — the member can view the conversation but cannot send messages.

The dropdown opens upward (side: "top") since the composer sits at the bottom of the viewport.

When a non-owner member has no phone assignments, the service returns an empty list. This means:

  • The compose bar does not render — the member cannot send SMS
  • The dial pad has no available phones — the member cannot place calls
  • The member can still view conversations and call history

This enforces least-privilege access. An admin must assign at least one phone before the member can communicate. Owners bypass this restriction and always have access to all org phones.

src/mods/settings/
├── services/
│ └── resolve_member_phones_service.rs # Core resolution logic
├── api/
│ └── get_member_allowed_phones_api.rs # GET /api/member/allowed-phones
└── types/
└── member_phone_type.rs # MemberPhoneSummary + helpers
# Enforcement points:
src/mods/call/api/initiate_call_api.rs
src/mods/messaging/api/send_message_api.rs
# UI components:
src/mods/call/components/dial_pad_panel_component.rs
src/mods/messaging/components/message_compose_component.rs