This chapter is where the prior six chapters finally cash out.
If you understand telecom but cannot express it as product architecture, you still do not have a company plan.
So let us turn the concepts into system design.
The product you are really building
SpeakOps should not think of itself as "an app with phone numbers."
A healthier framing is:
SpeakOps is a control plane for business voice behavior.
That control plane may depend on external telecom infrastructure, but it owns:
- intent
- policy
- customer-visible configuration
- observability
- workflow state
This framing leads to better architecture than centering everything around "calls" or "numbers" alone.
The five core objects I would model first
This is the founder-friendly control-plane view: separate number custody, routing policy, execution, provider adapters, workflow state, and observability.
1. Number
Represents the public identifier.
It should not collapse all related state into itself.
Fields may include:
- canonical E.164 form
- number type
- country
- status
- current customer assignment
2. Provider relationship
Represents the active service relationship for the number or call path.
Examples:
- current serving provider
- upstream programmable-voice vendor
- underlying carrier, if known
This should be separate from the number because providers can change while the number remains stable.
3. Routing policy
Represents the customer-configured behavior model.
Examples:
- schedules
- caller conditions
- actions
- fallback logic
This should be versioned and auditable.
4. Porting or migration state
Represents the transition process for numbers being onboarded or moved.
Examples:
- requested
- validating
- exception
- scheduled
- completed
Treat this as a workflow domain, not a boolean.
5. Call session and call legs
Represents live execution.
This is where you track:
- inbound leg
- downstream legs
- final disposition
- timing
- rule path taken
This object is operationally different from the static number object.
The biggest modeling trap
Do not store everything under one giant phone_number record.
Why?
Because you will eventually need to distinguish:
- the public number
- customer assignment
- number inventory source
- current provider
- historical provider
- routing behavior
- migration status
- trust/compliance status
If you mash those together, basic telecom events will feel like schema violations.
A healthy internal model
This is the rough structure I would want:
Customer
-> NumberAssignment
-> Number
-> ServiceProviderLink
-> RoutingPolicyVersion
-> ComplianceProfile
-> PortingWorkflow (optional)
CallSession
-> InboundLeg
-> RuleEvaluationTrace
-> OutboundLeg[]
-> FinalDisposition
-> Recording / Analytics / Audit references
This is not the only good model, but it reflects telecom reality much better than a flat CRUD table.
Why provider abstraction matters early
Even if you begin with one upstream provider, you should architect as if that will change.
Not because abstraction is fashionable, but because telecom dependencies shape your business options:
- number availability
- geographic support
- pricing
- porting quality
- feature support
- messaging approval success
- trust/reputation behavior
If the product core directly embeds vendor-specific assumptions, your roadmap will quietly shrink around one provider's worldview.
What a provider adapter should abstract
A good provider abstraction layer does not pretend every telecom provider is identical.
It should abstract:
- number inventory operations
- provisioning state changes
- inbound event normalization
- call-control actions
- recording availability
- porting status ingestion
But it should also surface capability differences explicitly.
In other words:
- abstract the common path
- expose feature gaps intentionally
Do not hide critical capability differences behind fake uniformity.
Routing evaluation should be traceable
If SpeakOps becomes a rules-heavy product, every call should leave behind a clear explanation of what happened.
Think like a debugger:
- which rule matched?
- what conditions were evaluated?
- which branches were skipped?
- why was the final action chosen?
- what downstream attempts were created?
Without that trace, support becomes folklore.
With it, you can build:
- customer-facing call explanations
- internal debugging tools
- analytics on rule effectiveness
Live call control needs a different reliability posture
A normal admin setting can tolerate eventual consistency and retries.
A live inbound call often cannot.
So you should separate:
Configuration plane
Admin UI, policy editing, customer setup, imports, audits.
Execution plane
Low-latency routing evaluation and call-control decisions.
Event plane
Analytics, CRM sync, recordings, notifications, post-call workflows.
That separation is one of the highest-ROI architecture decisions for a telephony startup.
Failure design matters as much as happy-path design
Every routing product needs explicit fallback semantics.
Ask:
- what if the rules engine times out?
- what if a downstream provider rejects the leg?
- what if a customer's config is invalid?
- what if the provider webhook is delayed or duplicated?
Telecom products earn trust by failing predictably.
That means:
- default routes
- timeout policies
- retry policies
- safe rejects
- operator-visible alerts
Observability is part of the product
For SpeakOps, observability should not be a backend afterthought.
It is one of the core product features.
You want to expose:
- number custody and provider state
- porting progress
- call session timelines
- rule evaluation paths
- downstream leg outcomes
- provider-specific errors
That makes your product feel authoritative rather than mysteriously dependent on carriers.
What I would build first
If you are exploring, I would prioritize:
- clean number and provider model
- robust inbound routing rules engine
- call-session tracing
- human-readable debugging timeline
- carrier abstraction for one current vendor and one future vendor
I would delay:
- fully generalized multi-carrier everything
- operator-grade billing complexity
- deep mobile-core ambitions
What you should be able to explain after this chapter
By the end of this chapter, you should be able to answer:
- What are the core domain objects in a telephony control plane?
- Why must number custody be separate from routing behavior?
- Why should configuration, execution, and event planes be separated?
- What observability primitives are essential for a credible voice product?
The next chapter covers a related but much stricter adjacent ecosystem:
messaging.