Microsoft Teams Phone Deployment: A Practical Rollout Guide for IT Pros


Executive Snapshot

Category Recommendation
Licensing Teams Phone Standard (add-on) or Teams Phone with Calling Plan (bundled)
PSTN Connectivity Microsoft Calling Plans for simplicity; Direct Routing or Operator Connect for existing SBC/carrier
Dial Plan Scope Tenant-level dial plan for normalisation; user-level only for exceptions
Emergency Calling Mandatory: configure dynamic emergency calling (E911/E999) before go-live
Device Provisioning Use TAC Remote Provisioning + Configuration Profiles for zero-touch
Pilot Size 5–10% of target user base before broad rollout
Rollback Strategy Keep legacy PBX in parallel for minimum 30 days post-cutover

TL;DR

flowchart TD
    A[Assign Teams Phone licenses] --> B{PSTN connectivity model}
    B -->|Microsoft| C[Calling Plan]
    B -->|Existing carrier| D[Direct Routing via SBC]
    B -->|Carrier partner| E[Operator Connect]
    C --> F[Assign phone numbers]
    D --> F
    E --> F
    F --> G[Configure calling & emergency policies]
    G --> H[Set voicemail & call queues]
    H --> I[Pilot test then roll out]
    classDef path fill:#2b3a67,stroke:#6f8fdc,color:#fff
    class C,D,E path
Licensing, PSTN model choice, number assignment, then policy configuration.
  • Microsoft Teams Phone deployment requires three sequential layers: licensing, PSTN connectivity, and dial plan configuration — skip any one and calls will silently fail.
  • Direct Routing gives the most flexibility for organisations with existing SBCs or carrier contracts, but adds SBC management overhead.
  • Operator Connect is the sweet spot for mid-market organisations wanting carrier-grade PSTN without managing SBC infrastructure.
  • Emergency calling configuration is not optional — many jurisdictions carry legal liability for misconfigured E911/E999 locations.
  • PowerShell is your friend: the Teams PowerShell module automates bulk licence assignment and dial plan policy at scale far faster than the Teams Admin Centre UI.

Introduction

Migrating from a legacy PBX to Microsoft Teams Phone is one of the highest-impact — and highest-risk — projects an IT team can undertake. Done well, it consolidates voice, video, chat, and PSTN calling into a single client, slashes telephony infrastructure costs, and eliminates an entire category of support tickets. Done poorly, it leaves executives unable to dial out on day one and you fielding angry calls on your mobile.

The challenge is that Microsoft Teams Phone deployment spans multiple control planes: Microsoft 365 licensing, Teams Admin Centre policies, PSTN carrier configuration, SBC routing tables, and physical or softphone device provisioning. Each layer has its own gotchas, and the official Microsoft documentation — while thorough — is scattered across dozens of articles.

This guide consolidates the full deployment sequence into a single, opinionated walkthrough. It assumes you already have a functioning Microsoft 365 tenant with Teams deployed, and focuses exclusively on the telephony layer. Whether you are deploying Calling Plans, Direct Routing, or Operator Connect, the core sequence applies.


Prerequisites

  • Global Admin or Teams Administrator role in Microsoft 365
  • Teams PowerShell module v5.x or later (Install-Module MicrosoftTeams)
  • Phone System (Teams Phone Standard) licences assigned or confirmed in procurement
  • PSTN connectivity decision made (Calling Plans / Direct Routing / Operator Connect)
  • For Direct Routing: a certified SBC (Audiocodes, Ribbon, Oracle, etc.) with a valid public TLS certificate
  • DNS control for your SIP domain (Direct Routing requires an A record for the SBC FQDN)
  • Network readiness assessment completed — QoS markings and bandwidth confirmed
  • Emergency address inventory (street-level addresses for all office locations and remote worker home cities)

Deployment Architecture Overview

Before writing a single PowerShell command, understand the three PSTN connectivity models and where each fits:

flowchart TD
    A[Teams Client / IP Phone] --> B[Microsoft Teams Service]
    B --> C{PSTN Connectivity Model}
    C -->|Option 1| D[Microsoft Calling Plans\nMicrosoft owns PSTN]
    C -->|Option 2| E[Operator Connect\nCarrier manages SBC in Azure]
    C -->|Option 3| F[Direct Routing\nYou manage certified SBC]
    F --> G[Your SBC / On-prem PBX]
    G --> H[PSTN / Legacy PBX]
    E --> H
    D --> H

    style D fill:#0078D4,color:#fff
    style E fill:#00BCF2,color:#fff
    style F fill:#005A9E,color:#fff

Calling Plans — Microsoft is your carrier. Fastest to deploy, highest per-minute cost, limited geographic availability. Best for: organisations with no existing carrier contracts, sub-500 users, and presence only in supported countries.

Operator Connect — Your chosen carrier (BT, Telstra, Deutsche Telekom, etc.) connects directly into Microsoft's network via a managed peering relationship. You assign numbers in the Teams Admin Centre. No SBC to manage. Best for: mid-market organisations wanting carrier flexibility without SBC overhead.

Direct Routing — You deploy and manage a certified Session Border Controller that bridges your carrier's SIP trunk to Microsoft Teams. Maximum flexibility, maximum control, maximum complexity. Best for: enterprises with existing SBC investments, complex dial plans, or countries not covered by Calling Plans.


Step 1 — Assign Licences

Every user who needs PSTN calling requires:

  1. A base Teams licence (Microsoft 365 E3/E5 or Teams Essentials)
  2. Teams Phone Standard add-on (previously called Phone System) — OR —
    Microsoft Teams Phone with Calling Plan (bundled licence that includes both Phone System and a Calling Plan)
  3. A Calling Plan licence if using Microsoft as carrier (Domestic, International, or Pay-As-You-Go)

Bulk licence assignment via PowerShell

# Connect to Microsoft Graph (requires Microsoft.Graph module)
Connect-MgGraph -Scopes "User.ReadWrite.All", "Directory.ReadWrite.All"

# Define the SKU IDs — retrieve your tenant's actual GUIDs with:
# Get-MgSubscribedSku | Select-Object SkuPartNumber, SkuId

$phoneSystemSkuId    = "e43b5b99-8dfb-405f-9987-dc307f34bcbd"  # MCOEV (Phone System)
$domesticPlanSkuId   = "o365_business_premium"                  # Replace with real GUID

# Import a CSV with a 'UserPrincipalName' column
$users = Import-Csv -Path "C:\deploy\teams-phone-users.csv"

foreach ($user in $users) {
    $userId = (Get-MgUser -UserId $user.UserPrincipalName).Id

    $licencePayload = @{
        addLicenses = @(
            @{ skuId = $phoneSystemSkuId },
            @{ skuId = $domesticPlanSkuId }
        )
        removeLicenses = @()
    }

    Set-MgUserLicense -UserId $userId -BodyParameter $licencePayload
    Write-Host "Licenced: $($user.UserPrincipalName)" -ForegroundColor Green
}

Tip: Licence propagation to the Teams service can take 15–30 minutes. Do not proceed to number assignment until Get-CsOnlineUser returns EnterpriseVoiceEnabled = True for your test user.


Step 2 — Configure PSTN Connectivity

Option A: Microsoft Calling Plans

Number assignment is the only configuration required:

# Connect to Teams PowerShell
Connect-MicrosoftTeams

# List available unassigned numbers in your tenant
Get-CsPhoneNumberAssignment -CapabilitiesContain CallingPlan -Unassigned

# Assign a number to a user
Set-CsPhoneNumberAssignment `
    -Identity "alice@contoso.com" `
    -PhoneNumber "+14255550101" `
    -PhoneNumberType CallingPlan

Option B: Operator Connect

Operator Connect is configured almost entirely in the Teams Admin Centre (Voice > Operator Connect), but you can verify the assignment via PowerShell:

# Verify Operator Connect number assignment
Get-CsPhoneNumberAssignment -AssignedPstnTargetId "alice@contoso.com"

# If the carrier has provisioned the number, assign it
Set-CsPhoneNumberAssignment `
    -Identity "alice@contoso.com" `
    -PhoneNumber "+441632960101" `
    -PhoneNumberType OperatorConnect

Option C: Direct Routing — SBC Pairing

# Register your SBC with Microsoft Teams
New-CsOnlinePSTNGateway `
    -Fqdn "sbc01.contoso.com" `
    -SipSignalingPort 5067 `
    -ForwardCallHistory $true `
    -ForwardPai $true `
    -SendSipOptions $true `
    -MaxConcurrentSessions 100 `
    -Enabled $true

# Create a PSTN usage record
Set-CsOnlinePstnUsage -Usage @{Add = "UK_Internal"}

# Create a voice route
New-CsOnlineVoiceRoute `
    -Name "UK-National" `
    -NumberPattern "^\+44[1-9]\d{8,9}$" `
    -OnlinePstnGatewayList "sbc01.contoso.com" `
    -Priority 1 `
    -OnlinePstnUsages "UK_Internal"

# Create a voice routing policy
New-CsOnlineVoiceRoutingPolicy `
    -Identity "UK-Staff-VRP" `
    -OnlinePstnUsages "UK_Internal"

# Assign the routing policy to a user
Grant-CsOnlineVoiceRoutingPolicy `
    -Identity "alice@contoso.com" `
    -PolicyName "UK-Staff-VRP"

# Enable Enterprise Voice for the user
Set-CsPhoneNumberAssignment `
    -Identity "alice@contoso.com" `
    -PhoneNumber "+441632960101" `
    -PhoneNumberType DirectRouting

Step 3 — Configure Dial Plans

Dial plans normalise dialled numbers into E.164 format (+<country><number>). Without correct normalisation, short-form internal extensions and local dialling patterns will fail.

flowchart LR
    A[User dials\n020 7946 0012] --> B[Tenant Dial Plan\nnormalisation rules]
    B --> C[E.164 Format\n+442079460012]
    C --> D[Voice Route Match\n^\+44 prefix]
    D --> E[SBC / Calling Plan\nPSTN egress]

Create a tenant-level dial plan for UK users

# Define normalisation rules
$rule1 = New-CsVoiceNormalizationRule `
    -Name "UK-Local-London" `
    -Pattern "^0(20\d{8})$" `
    -Translation '+44$1' `
    -InMemory

$rule2 = New-CsVoiceNormalizationRule `
    -Name "UK-National" `
    -Pattern "^0([1-9]\d{8,9})$" `
    -Translation '+44$1' `
    -InMemory

$rule3 = New-CsVoiceNormalizationRule `
    -Name "UK-International" `
    -Pattern "^00(\d+)$" `
    -Translation '+$1' `
    -InMemory

# Create the dial plan
New-CsTenantDialPlan `
    -Identity "DP-UK-Standard" `
    -NormalizationRules @($rule1, $rule2, $rule3) `
    -Description "Standard UK dial plan with E.164 normalisation"

# Assign to users (or assign to the tenant as default)
Grant-CsTenantDialPlan `
    -Identity "alice@contoso.com" `
    -PolicyName "DP-UK-Standard"

# Bulk assign via pipeline
Get-CsOnlineUser -Filter {CountryAbbreviation -eq "GB"} |
    Grant-CsTenantDialPlan -PolicyName "DP-UK-Standard"

Test normalisation before go-live: In the Teams Admin Centre go to Voice > Dial plans, select your plan, and use the Test dial plan tool to validate each dialling pattern.


Step 4 — Configure Emergency Calling

Emergency calling configuration is frequently skipped during pilot phases and causes compliance failures at go-live. In the US, the Ray Baum's Act requires dispatchable location to be transmitted with E911 calls. The UK has equivalent requirements under Ofcom guidance.

# Create an emergency address
$address = New-CsOnlineLisCivicAddress `
    -CompanyName "Contoso Ltd" `
    -Address "1 Fenchurch Street" `
    -City "London" `
    -StateOrProvince "England" `
    -CountryOrRegion "GB" `
    -PostalCode "EC3M 3BY" `
    -Elin "441632960911"   # Emergency Location Identification Number (Direct Routing only)

# Associate a network subnet with the address (dynamic emergency calling)
New-CsOnlineLisSubnet `
    -Subnet "10.10.20.0" `
    -Description "London HQ Floor 3" `
    -LocationId $address.LocationId

# Create an emergency calling policy
New-CsTeamsEmergencyCallingPolicy `
    -Identity "ECP-LondonHQ" `
    -EmergencyNumbers @{EmergencyDialString="999";EmergencyDialMask="112;911"} `
    -NotificationGroup "security-team@contoso.com" `
    -NotificationMode NotificationOnly

# Assign the policy
Grant-CsTeamsEmergencyCallingPolicy `
    -Identity "alice@contoso.com" `
    -PolicyName "ECP-LondonHQ"

Step 5 — Device Provisioning

For organisations deploying certified Teams IP phones (Yealink, Poly, AudioCodes, Cisco), use Remote Provisioning in the Teams Admin Centre to achieve zero-touch deployment.

flowchart TD
    A[IT Pro imports\nMAC addresses to TAC] --> B[Phones ship to\nuser desks — unboxed]
    B --> C[Phone boots,\ncontacts Microsoft provisioning service]
    C --> D[TAC pushes\nConfiguration Profile]
    D --> E[Phone signs in\nas assigned Teams user]
    E --> F[User makes\nfirst PSTN call]

PowerShell: Bulk-import phone devices

# Import certified Teams phones by MAC address from CSV
# CSV format: MacAddress, Description, ConfigurationProfileId

$devices = Import-Csv -Path "C:\deploy\phones.csv"

foreach ($device in $devices) {
    New-CsOnlineVoiceUser -Identity $device.MacAddress  # placeholder; use TAC bulk import for production

    # Assign a configuration profile (created in TAC > Devices > Configuration profiles)
    Set-CsTeamsIPPhonePolicy `
        -Identity "IPPhonePolicy-DeskPhone" `
        -SignInMode UserSignIn `
        -HotDeskingIdleTimeoutInMinutes 120 `
        -AllowHotDesking $true
}

# Apply phone policy to a user
Grant-CsTeamsIPPhonePolicy `
    -Identity "alice@contoso.com" `
    -PolicyName "IPPhonePolicy-DeskPhone"

For softphone-only deployments (Teams desktop client), no device provisioning is needed — the calling features activate automatically once Phone System is licenced and a number is assigned.


Step 6 — Validate and Test

Never go broad without a structured validation checklist:

Test Command / Method Pass Criteria
Licence propagation Get-CsOnlineUser -Identity alice@contoso.com | Select EnterpriseVoiceEnabled True
Outbound PSTN call Dial external mobile from Teams Call connects, audio both ways
Inbound PSTN call Dial assigned DDI from external phone Teams client rings
Emergency call (test) Dial 933 (US test number) or coordinate with carrier Correct location returned
Dial plan normalisation TAC > Dial plans > Test dial plan E.164 output matches expected
Voicemail Let call ring to voicemail Voicemail deposited, transcription available
Call queue / Auto attendant Call main number, select menu option Correct routing observed

Troubleshooting

❌ "Your phone number isn't set up yet" in Teams client

Cause: Phone System licence not yet propagated, or EnterpriseVoiceEnabled is still False.

# Check current voice state
Get-CsOnlineUser -Identity "alice@contoso.com" |
    Select-Object DisplayName, EnterpriseVoiceEnabled, LineUri, OnlineVoiceRoutingPolicy

# Force a policy refresh (wait 15 min after licence assignment before running)
Invoke-CsOnlineUserUpdate -Identity "alice@contoso.com"  # Deprecated in newer module — re-assign policy instead
Grant-CsOnlineVoiceRoutingPolicy -Identity "alice@contoso.com" -PolicyName "UK-Staff-VRP"

❌ Outbound calls fail with SIP 404 / "We couldn't complete your call"

Cause (Direct Routing): Number pattern in voice route does not match the E.164 number after dial plan normalisation.

# Verify the route's regex against a specific number
$route = Get-CsOnlineVoiceRoute -Identity "UK-National"
$testNumber = "+441632960101"
if ($testNumber -match $route.NumberPattern) {
    Write-Host "MATCH — route will fire" -ForegroundColor Green
} else {
    Write-Host "NO MATCH — update NumberPattern" -ForegroundColor Red
}

❌ SBC shows "Not connected" in Teams Admin Centre

Cause: TLS mutual authentication failure — typically an expired or mismatched certificate on the SBC, or the SBC's FQDN does not resolve publicly.

  • Verify the SBC certificate is issued by a Microsoft-trusted CA (Baltimore CyberTrust, DigiCert, etc.)
  • Check SIP port 5067 (TLS) is open inbound from Microsoft IP ranges: 52.112.0.0/14 and 52.122.0.0/15
  • Confirm DNS A record for sbc01.contoso.com resolves to the SBC's public IP

❌ Emergency calls route to wrong PSAP

Cause: User's network subnet is not mapped to a civic address, so the call uses the tenant's registered address instead of the user's actual location.

# Audit unmapped subnets
$subnets = Get-CsOnlineLisSubnet
$subnets | Where-Object { $_.LocationId -eq $null } | Select-Object Subnet, Description

Pitfalls to Avoid

  1. Cutting over before validating voicemail. Cloud Voicemail requires an Exchange Online mailbox. Users with Exchange on-premises in hybrid need Set-CsUser -HostedVoiceMail $true explicitly.

  2. Assigning numbers before licences propagate. Number assignment against an unlicenced user silently succeeds but calling never works. Always check EnterpriseVoiceEnabled = True first.

  3. Overlapping number patterns in voice routes. If two routes share overlapping regex patterns, calls route to whichever has lower priority — often unexpectedly. Use the Call Analytics tool in TAC to trace misdirected calls.

  4. Forgetting to configure auto-attendant business hours. The default behaviour for unset hours is to play a generic message and disconnect — not transfer to voicemail. Configure this explicitly before users notice.

  5. Using the Global (org-wide default) voice routing policy for pilot users. If your pilot users are on the Global policy and you update it, you affect every Teams-enabled user in the tenant. Always create a named policy for pilots.

  6. Not documenting SBC dial peer configuration. When an SBC firmware update changes TLS behaviour 18 months post-deployment, you need those original configuration artefacts.


Key Takeaways

  • Sequence matters: licence → PSTN connectivity → dial plan → emergency calling → device provisioning. Reversing or skipping steps creates hard-to-diagnose failures.
  • PowerShell at scale beats the TAC UI for anything above 20 users — build reusable scripts and keep them in source control.
  • Direct Routing SBC pairing requires a publicly trusted TLS certificate — self-signed certificates are not supported and will prevent the SBC from appearing online.
  • Dynamic emergency calling via network subnet mapping is the only way to comply with dispatchable location requirements for remote or mobile workers.
  • Parallel-run your legacy PBX for 30 days minimum — DDI porting timelines and user adoption issues will surface problems you did not anticipate in testing.
  • Call Analytics and the Call Quality Dashboard (CQD) are your post-deployment best friends — enable them before go-live, not after your first complaint.
  • Operator Connect is the fastest path to production for organisations that want carrier-grade reliability without SBC management burden.

Next Steps

  1. Configure Call Queues and Auto Attendants — route your main company numbers to IVR menus and agent queues in Teams Admin Centre > Voice > Auto attendants.
  2. Enable Call Quality Dashboard — navigate to https://cqd.teams.microsoft.com and import your building/subnet data for location-aware quality reporting.
  3. Set up Microsoft Teams Rooms (MTR) for shared spaces — conference rooms need a dedicated resource account, a Teams Rooms licence, and a separate IP phone or MTR device policy.
  4. Review Calling Policies — control who can make private calls, use call forwarding, or delegate calls via CsTeamsCallingPolicy.
  5. Plan your number porting — Microsoft's Number Porting Wizard in TAC handles Letter of Authorisation (LOA) submission; allow 2–4 weeks for UK/US ports from incumbent carriers.

Related Articles

  • [Placeholder] Microsoft Teams Direct Routing: SBC Configuration Deep Dive/microsoft-365/teams-direct-routing-sbc-configuration/
  • [Placeholder] Teams Call Quality Dashboard: Building a Proactive Monitoring Workflow/microsoft-365/teams-call-quality-dashboard-monitoring/
  • [Placeholder] Microsoft 365 Licensing Explained: Phone System, Calling Plans, and Teams Add-ons Compared/microsoft-365/microsoft-365-teams-phone-licensing-guide/
Thorsteinn Halldorsson Senior Cloud Engineer

Senior Cloud Engineer with 25+ years of hands-on experience across the datacenter-to-cloud stack: fiber SAN and disk storage, IBM/Lenovo blade and Dell/HP/Lenovo servers, Hyper-V and VMware clusters, and SQL and Remote Desktop Services (RDS) clusters. Deep in the Microsoft platform — Active Directory, PKI/certificate services, SQL, Power BI, Dynamics 365 Business Central (NAV) and AX (Axapta), Microsoft 365, Entra, and Intune — with a focus on Azure operations, FinOps, and applying AI tools like GitHub Copilot and Claude in real workflows. Writes practical, no-nonsense guides for IT professionals who need to ship real solutions.

Leave a Reply

Your email address will not be published. Required fields are marked *