Skip to content
megapromotingLet's talk

Expertise · Telephony and contact centre

The telephony layer between your operator and whoever answers — a human or an agent.

We build the phone system: SIP trunks, schedule-based routing rules, IVR menus read from the database, waiting queues, transfer to a human, call recording and analysis. The call becomes a record with transcript and summary, not a memory.

Already builtThree in-house implementations, cited below. (1) The Asterisk layer in the Kallina platform: 5,522 lines of dialplan configuration, AGI scripts and audio bridges in `asterisk/`, plus automatically generated trunk templates per number. (2) `asterisk-manager` — a separate service that manages queues, trunks and RTP flow through the PBX management interface. (3) The middleware for a virtual switchboard of an operator, which ran in production: the reconciliation commit of 14 July 2026 brings into the repository code that had been running on the server and was about 40 days ahead of git. The reserve, stated before you ask: the SIP host through which our test lines pass does not respond — checked today, 06.09.2026, with no reply to ping and no reply over HTTP. We do not present a demo number to call now, because we would not be able to bring it up in front of you.

Between your telephone operator and whoever actually answers — a person or a voice agent — there must be a layer that makes decisions. Who takes the call at 23:40. What happens if nobody answers. Where the call goes when the client asks for “a human”. What remains of the conversation after it has ended. This layer is a central exchange, and we build it so that it belongs to the business, not to the voice supplier: if tomorrow you change the agent engine, the routing rules, queues and history remain with you.

Concretely, what we built: twelve dialplan files — production, IVR, queue, transfer, voicemail, forwarding, outbound calls with agent — four AGI scripts in Python and two audio bridges, 5,522 lines in total. The IVR engine does not have the menus written in the file: it reads them from the database through an AGI script and returns one of six decisions — to an agent, to another menu, to a queue, transfer to an external number, final message, or closure. The queues are named after their identifier and do not have members written in the configuration: they are added and removed from outside, through the management interface, which means that an operator can enter or leave the queue without restarting the exchange.

Routing has real rules, not a single “ring here”: business hours and time zone, overnight schedules such as 22:00 → 06:00, and priority matching — the SIP `Diversion` header, which carries the original number from which the call was forwarded, then the own number, then the fallback rule. Trunks are generated per number, with a name built from the provider and number, by a configuration function — not handwritten for each new line.

What comes out of a call is just as important as the call. For the line tied to an operator’s virtual exchange, we built a Python middleware that queries the recordings list every minute, with a broad reconciliation every six hours so a network outage does not lose anything, and a three-minute scan of missed calls — those have no recording and would otherwise be completely invisible. Each recording is downloaded, transcribed, analysed and mirrored in call analytics, and the missed call becomes an alert. Idempotence is held in a Redis set, so the same recording is not processed twice.

What it covers

The work, by component

The number enters a generated trunk, not a handwritten one

Trunk templates are parameterised and filled in by a configuration function, with a name formed from the provider and number. The template contains transport, allowed codecs, NAT handling, DTMF mode and authentication. The practical result: the tenth number connects just like the first, and the differences between operators live in one place.

The exchange decides where the call goes, according to written rules

Business hours and time zone, overnight schedules such as 22:00 → 06:00, priority matching: the SIP `Diversion` header (the number from which it was forwarded), then the own number, then the fallback rule. Rule lookup is done by an AGI script that queries the platform during the call, with its own timeout — so a rule changed in the interface applies to the next call, without a restart.

The call reaches the agent, the queue or the person

To the voice agent, the audio passes through a bridge that converts the codec both ways, `g711_ulaw` ↔ `PCM16`. To people, the call enters an externally managed queue. And transfer is at the speaker’s fingertips: blind transfer by `##` and attended transfer by `*2`, with the call landing in a context written for this, which distinguishes four-digit internal extensions from external numbers.

IVR menu read from the database, not from a file

The IVR engine receives the menu identifier and reads it from the database through an AGI script. The result is one of six decisions: to an agent, to another menu (recursive), to a queue, external transfer, final message, closing. Messages can be synthesised or can be pre-recorded audio files. Changing a menu means changing a row in the database, not editing a configuration file on the server.

Queues with dynamic members

Each queue carries the name derived from its identifier. Members are not persisted in the configuration: they are added and removed at runtime through the exchange management interface, and simultaneous placement on multiple free spots is enabled. In practice, an operator enters or leaves the queue without the exchange being restarted and without waiting calls being affected.

Dispatch to people in the field

An agent can call someone outside and return with the answer. The mechanism is implemented with a dedicated table and explicit states — calling, in progress, retrying, completed, failed, no answer, expired — with a maximum of three attempts and a two-minute pause between them, and the result (including the estimated time of arrival) is returned in a structured form to the conversation that requested it. It is not a concept: there are five dedicated server functions, plus a sweep of calls left hanging.

Calls become data, including those to which nobody answered

A scheduled process reads the recordings list every minute, with broad reconciliation every six hours so an outage does not lose anything. The recording is downloaded in WAV, transcribed, analysed and mirrored in call analytics together with the audio. Separately, every three minutes the unanswered calls are read — which have no recording and otherwise would exist nowhere — and become an alert. A missed call is a lost client; making it visible is the cheapest improvement in a call centre.

Brakes against the operator API

The client that speaks to the operator’s exchange has its own rate limiter, with a token bucket, and explicit error handling. It is not a theoretical precaution: a middleware that queries every minute and reconciles every six hours can, without a brake, hit the operator limit and be blocked exactly when you need it.

What it looks like

The path, step by step.

01

Inventory of lines before any configuration

Which numbers exist, with which operator, who is responsible for each today, on what schedule, and what happens now when nobody answers. It may seem like bureaucracy; it is the part that prevents surprises. From our experience, in a number pool there are almost always lines where the database says one thing and the PBX says another — and they are not discovered at launch, but now.

02

Trunk, then test both ways, separately

The number enters via the trunk, and incoming and outgoing calls are tested as two separate things, because they fail separately. We have documented a case where incoming calls worked perfectly for days, while every outgoing call was rejected by the operator’s PBX with `403 Forbidden`, with no change on our side. A single test of “it rang and it worked” does not cover that.

03

Dialplan: routing, IVR, queue, transfer, voicemail

The rules for schedule and priority, the menus, queues and transfer paths are written as dialplan and as database rows, not as a verbal understanding. At the end you know exactly what happens to a call at 23:40, on Saturday, when the agent does not understand the request.

04

Calls enter your systems

The call result — transcript, summary, sentiment, recording address, duration — is sent on via a signed webhook, and the organisation is identified by phone number. Where there is a CRM, the call is linked to the record and can automatically move the status; we write about this on the CRM and sales automation page.

05

Continuity is discussed at the start, not after the first outage

A PBX on a single host is a single point of failure, and we have experienced exactly that: when the host does not respond, all the lines passing through it go down with it, regardless of how well the agent is configured. The failure signature is clear — the call returns `request timed out` with an empty SIP call identifier, meaning the SIP call was never established. That is why, in a real project, the question “what happens when the host goes down” is asked and budgeted from the start: a second host, monitoring that calls a person, and a fallback path to regular numbers.

Ce vede utilizatorulThe applicationNetwork and securityHosting and dataEVERY LAYER, CHOSEN AND EXPLAINED
The layers of a phone line: the operator trunk, the exchange with routing rules, the audio bridge to the agent, the queue and transfer to a human, then recording, transcription and analysis.

The data

What we touch, where it lives, and how long it stays

The questions anyone with a data protection officer would ask — asked here before they do.

Where call records end up
The PBX call log is written today locally, in CSV format, on the PBX host; writing it to a PostgreSQL database is prepared in the configuration, but remains disabled. We say this because it has a direct consequence: if you want reports on calls outside the PBX, enabling database logging is work to be done, not a checkbox. Transcripts and analyses stay separate, in the platform, linked to your workspace.
What is stored from a call
The caller number and called number, time, duration, result, audio recording, transcript and analysis. For missed calls there are the number, time and reason for no answer — there is no audio, because none was created. The recording is downloaded in WAV at 8 kHz, mono, and converted to a compressed format for delivery to people.
Idempotence, so data is not duplicated
Each processed record is marked in a Redis set. Broad reconciliation can reread the same time window without resending anything. It is the detail that makes the difference between a system that can be restarted calmly and one that, on every restart, sends yesterday’s alerts again.
Who can listen to a recording
Call data is tied to the business workspace, and access goes through authentication. There is no shared repository across clients and there is no “from the platform” access without an identity. Exactly which member of your team has the right to listen is set during implementation, not by default.
Retention and the recording notice are your decisions
How long audio, the transcript and the call log are kept, and what is said at the start of the conversation about the fact that it is being recorded, are decisions of the data controller — that is, yours. Deletion on request is implemented in the platform; automatic deletion at the deadline is done today by procedure, not by a clock, so if you need it automatic, it comes in as project work.

A case

Long calls disappeared. Short ones did not.

The situation

In a call analysis flow, some calls reached analysis and some did not. The pattern was what gave away the cause: it was exactly the long calls that were missing. A defect that is reported as “sometimes it does not work” and is searched for, wrongly, in transcription.

What we built

There were two transcription paths and both failed, for different reasons. The multimodal path sends the entire audio file encoded in the body of the request; above a certain size, the intermediate server body limit responded with 413. The fallback path requested a transcription model that the access key no longer allowed, so it responded with 403. Both failed, the process reported that all transcription models had failed, and the call was abandoned. Short calls stayed under the body limit, so they passed — hence the pattern. The fix was: skip the multimodal path for WAV files over 12 MB (an environment-configurable threshold), reuse the transcript already calculated instead of transcribing a second time, and move the last-resort transcription to an allowed model.

What came out

Verified on the recording that remained blocked: a 21-minute conversation, 41 MB WAV file — multimodal path bypassed, transcript taken, analysis generated, row and audio reached call analysis, zero failures. Reconciliation then confirmed that there are no other blocked recordings left.

What the case does not say

The 12 MB threshold is a property of the intermediate server, not of the call. When the gateway, key or model changes, the threshold must be rechecked — which is why we made it environment-configurable, not written into code.

Questions

What people ask us before they call

Do you have a number I can call now, so I can hear the agent?

Not today, and we would rather say that than give a number that rings out. The agent part can be listened to in the page, immediately. The phone part depends on a SIP host, and the host through which our test lines pass does not respond at the time of writing — checked today, no ping response and no HTTP response. For one of your projects, telephony is set up on a host dedicated to the project, not on the test one.

Why a private PBX and not directly the voice agent provider?

Because routing rules, queues, transfer, voicemail and call history belong to the business, not to the voice provider. With your own PBX you can change the agent engine without rewriting how your phone behaves, you can send the same call sometimes to an agent, sometimes to a person, and you can keep the call log with you. Without it, you are tied to what the provider chooses to expose.

What happens if the PBX server goes down?

All lines that pass through it go down. This is not a hypothesis: it is what we experienced, and the signature is easy to recognise — the call returns “request timed out” with an empty SIP call identifier, so it is not the fault of the agent, the number or the prompt. The conclusion we drew and now put into every project: continuity is not a function of the PBX, it is an architecture and budget decision, made at the start. It is solved with a second host and a fallback route to ordinary numbers, not with a setting.

Do outgoing calls work reliably if incoming calls work?

No. They are two different things, and the operator can treat them differently. We have a documented case in which, with our configuration unchanged and incoming calls working, the operator’s PBX started rejecting all outgoing calls with `403 Forbidden` — the proof that it was on the operator’s side was that a second account, with a structurally identical configuration, continued to place outgoing calls. That is why we do not promise an outgoing calling campaign before a successful outbound test on your number.

Can you work with the virtual PBX that the operator gives me?

Yes, and we have done it: we wrote Python middleware over the API of an operator’s virtual PBX, with our own client, rate limiter, recording download, transcription, analysis and alerting, running in production. What should be known in advance: access to the extended API is often a separate service, contracted separately, and the credentials may not work on the first attempt — with us, the cycle of clarifying the specification and resetting the password with the operator took months, with the service already billed. That is why, in an offer that depends on an operator’s API, we state the condition explicitly: the work starts after authentication has been demonstrated, not after it has been promised.

Are calls recorded, and who can listen to them?

They can be recorded, transcribed, and analysed. They remain tied to your business workspace, and access requires authentication — there is no shared pool across clients. Who on your team has listening rights is set during implementation. Notifying the other party and the legal basis for recording are your decisions as the data controller; we write them into the scenario, we do not assume them.

What happens to calls that no one answers?

They are the worst-handled case in most companies, because they leave no trace: no recording, no transcript, only a call record exists. With us, a process reads the record every three minutes, identifies unanswered calls, and turns them into an alert and a visible row. To be clear about what this depends on: we see them only to the extent that the controller exposes them in their record.

Can you make menus like “press 1 for...”?

Yes, and the menus are stored in the database, not in files on the server. The engine reads the menu configuration during the call and chooses one of six paths: to an agent, to another menu, to a queue, transfer to an external number, a final message, or hang-up. Messages can be synthesised or recorded in advance. In practice, a menu change does not require intervention on the switchboard.

How do I know if a call was lost on the way, between the switchboard and analysis?

Through reconciliation, and it is a piece we build explicitly. The fast query has a window of a few dozen hours, and over it runs every six hours a reconciliation over a one-week window, which rereads several result pages. Each processed record is marked, so rereading does not duplicate anything. Without reconciliation, a one-hour outage means a permanent gap in the data — and nobody notices it.

What the statements above rest on (22 sources)

22 of them are code and files from our repositories. We don't publish their name or line: together, on a single page, they would describe too precisely how systems that aren't only ours are built. We go through them with you, in the repository, on request — verification stays possible, it just happens in a conversation.

What would you want to work better?

Tell us about your process. Together we decide what is worth building, what we can connect, and how we check the result.

Let's talk