- Was ist sipgate.io?
- In diesem Tutorial
- Vorteile einer intelligenten Anrufweiterleitung
- Voraussetzungen
- Einblick in die Projektstruktur
- Step 1: das Projekt aufsetzen
- Step 2: das Projekt ausführen
- Step 3: Code Walkthrough
- Fazit
Was ist sipgate.io?
SMS oder Faxe senden und empfangen, den Anrufverlauf abrufen, Anrufe initiieren und manipulieren – das alles kann sipgate.io! Mit unseren APIs können Sie unsere Telekommunikationsfunktionen flexibel in Ihre Projekte integrieren. Unsere Library und Tutorials unterstützen Sie dabei, Ihre Telefonie möglichst bequem zu gestalten.
In diesem Tutorial
Erfahren Sie in dieser Schritt-für-Schritt-Anleitung, wie Sie einen kleinen Server einrichten und die empfangenen Call-Events verarbeiten, wodurch die Anrufenden automatisch an eine präferierte Service-Nummer weitergeleitet werden.
Den vollständigen Code für dieses Tutorial finden Sie in unserem GitHub.
Vorteile einer intelligenten Anrufweiterleitung
Mit unserer intelligenten Anrufweiterleitung können Sie beispielsweise Kund:innen immer an vertraute Berater:innen durchstellen. Die Anrufweiterleitung findet anhand ermittelter Statistiken automatisch statt. Dies verbessert nicht nur die Customer Experience mit Ihrem Unternehmen, sondern erhöht auch die Effizienz in Ihrem Serviceteam.
Voraussetzungen
Bevor wir uns mit dem Code auseinandersetzen, richten Sie sich ein sipgate-Konto für sipgate.io ein. Außerdem benötigen Sie für dieses Projekt mindestens 3 sipgate Rufnummern. Dabei dient eine Rufnummer als zentrale Hotline-Nummer, die auf die weiteren Service-Rufnummern weiterleitet. Mehr über Telefonie mit sipgate erfahren Sie auf der Website.
Einblick in die Projektstruktur
Um einen besseren Überblick über dieses Projekt zu bekommen, definieren wir folgende Bestandteile:
- index.ts: initialisiert die Datenbank und den Webhook-Server und empfängt die von der Push-API gesendeten Events.
- logic.ts: beinhaltet den Algorithmus zur intelligenten Anrufweiterleitung, empfängt und verarbeitet die Anrufinformationen von sipgate.io. Wir benutzen die sipgate.io Node Library, um mit sipgate zu kommunizieren.
- db.ts: In diesem Tutorial verwenden wir MySQL als Datenbank. Hier werden Einträge gespeichert, bestehend aus Zeitstempel, Anrufnummer und Servicenummer.
Um eine konsistente und einfache Einrichtung sowohl in der Entwicklungs- als auch in der Produktionsumgebung zu gewährleisten, verwenden wir einen Docker-Container.
Step 1: das Projekt aufsetzen
Docker installieren
Um verschiedene Container für die Installation der Abhängigkeiten zu orchestrieren und einzurichten, sind Docker und Docker Compose erforderlich. Bitte folgen Sie den Anweisungen auf der Seite Get Docker & Install Docker Compose für Ihr System. Danach ist Ihr System bereit, mehrere virtuelle Container als Multicontainer-Anwendung zu hosten.
Für Mac User: Öffnen Sie Ihre installierte Docker-Anwendung und überprüfen Sie, ob Docker läuft. Installieren Sie zusätzlich Homebrew, um die folgenden Befehle ausführen zu können:
brew install coreutils
ln -s /usr/local/bin/greadlink /usr/local/bin/readlink
Port-Forwarding
Webhooks und Umgebungsvariablen
Für die Integration mit sipgate.io führen Sie folgende Schritte durch:
- Konfigurieren Sie die Webhook-URLs für Ereignisse in beide Richtungen. Dies ist erforderlich, um die sipgate Push-API zu verwenden.
- Definieren Sie folgende Umgebungsvariablen:
# Use the service localhost.run or ngrok which set up a reverse ssh tunnel that forwards traffic from a public URL to your localhost
# Enter the public URL that was generated by localhost.run or ngrok.
WEBHOOK_URL="https://xxxxxxxxxxxxxxxxxxxxxx"
# Your Personal-Access-Token and the ID.
# Open https://app.sipgate.com and create a new token by navigating to "Benutzereinstellungen" / "User Settings" -> "Personal-Access-Tokens".
# The token should have the `numbers:read` and `devices:read` scopes (see https://sipgate.io/rest-api/authentication#personalAccessToken for more information).
TOKEN_ID='YOUR_SIPGATE_TOKEN_ID'
TOKEN='YOUR_SIPGATE_TOKEN'
# The central phone number that is called by clients.
CENTRAL_SERVICE_PHONE="+49xxxxxxxxxxxx0"
# Choose a database configuration.
# The Docker container will use these values to create the database.
DATABASE_HOST=localhost
DATABASE_NAME=database
DATABASE_USERNAME=admin
DATABASE_PASSWORT=root
DATABASE_ROOT_PASSWORD=root
DATABASE_TIMEZONE=Europe/Berlin
Hierbei ist die Variable SERVICE_PHONES ein String der Rufnummern Ihrer Mitarbeiter:innen und CENTRAL_SERVICE_PHONE ist die Hotline-Rufnummer. In diesem Schritt definieren wir auch die Umgebungsvariablen von unserer Datenbank.
📝Anmerkung: Wenn Sie den Port ändern, ändern Sie den Port in der Datei docker-compose.yml. Gehen Sie zu den „Webhooks“-Optionen und setzen Sie die incoming und outgoing URLs auf die Server-Adresse.
- Benennen Sie die Datei .env.example in .env um:
mv .env.example .env
📝Anmerkung: Wenn Sie die MySQL-Datenbank in der .env Datei ändern, geben Sie auch das Datenbankmodell in der Datei docker-compose.yaml ein.
Step 2: das Projekt ausführen
Nachdem Sie die Umgebungsvariablen eingerichtet haben, können Sie das Projekt auf folgende Weise ausführen:
sudo docker-compose up -d
📝Anmerkung: In diesem Beispiel verwenden wir die sipgate Softphones, allerdings ist es auch möglich andere Telefongeräte einzusetzen.
Um die notwendigen Abhängigkeiten zu installieren, führen wir den Befehl npm i aus. Im nächsten Schritt starten wir das Projekt mit npm run start.
Step 3: Code Walkthrough
In diesem Abschnitt schauen wir uns die wichtigsten Funktionen vom Code an. Die weiterhin besprochenen Code-Abschnitte sind aus dem Github-Repository entnommen.
index.ts
Der erste Abschnitt, auf den wir eingehen, zeigt die Initialisierung unserer Datenbank:
db.initialize().then(async () => {
console.log('Database initialized :card_file_box:');
db.synchronize();
});
Mehr Informationen über unsere Datenbank finden Sie in der db.ts -Datei. Als einzige Tabelle nutzen wir die CallHistory, welche später zur Speicherung der angenommenen Anrufe zwischen Kund:innen und Servicemitarbeiter:innen dient.
@Entity()
export class CallHistory {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({
type: 'datetime',
default: () => 'NOW()',
})
timestamp: Date;
@Column()
customerPhone: string;
@Column()
servicePhone: string;
}
📝Anmerkung: In unserem Beispiel beinhaltet unsere Datenbank nur die nötigsten Informationen für unser Projekt. Natürlich können Sie die Datenbankeinträge so anpassen, wie für Ihre Anwendung am sinnvollsten ist.
Im nächsten Schritt starten wir den webhookServer wie folgt:
const webhookModule = createWebhookModule();
async function main() {
const client = sipgateIO({
tokenId: personalAccessTokenId,
token: personalAccessToken,
});
...
const webhookModule = createWebhookModule();
const webhookServer = await webhookModule.createServer({
port: 8080,
serverAddress,
hostname,
});
console.log(
`Server running at ${serverAddress}\n` +
'Please set this URL for incoming calls at https://console.sipgate.com/webhooks/urls\n' +
"ProTip: To see how to do that automatically, check out the example at 'examples/settings/settings_set_url_incoming.ts'\n" +
'Ready for calls 📞',
);
webhookServer.onNewCall(async (newCallEvent) => {
const onlineNumbers = await getOnlineNumbers(
client,
authenticatedWebuser,
numbers.items,
);
return respondToNewCall(newCallEvent, centralPhone, db, onlineNumbers);
});
webhookServer.onAnswer(async (newAnswerEvent) =>
respondToOnAnswer(newAnswerEvent, centralPhone, db),
);
}
Dabei erstellen wir zuerst einen eigenen webhookServer, der die Call-Events von der Push-API erwartet. In unserem Beispiel reagieren wir auf newCall– und newAnswer-Events. Sobald wir ein Event empfangen, lösen wir den Algorithmus zur intelligenten Anrufweiterleitung aus der logic.ts-Datei aus.
Die getOnlineNumbers-Funktion sucht mithilfe der sipgateio Library die Telefonnummern der Geräte heraus, die zurzeit aktiv sind:
async function getOnlineNumbers(
client: SipgateIOClient,
authenticatedWebuser: string,
numbers: NumberResponseItem[],
) {
const devicesModule = createDevicesModule(client);
const devices = await devicesModule.getDevices(authenticatedWebuser);
const onlineDevices: Device[] = devices.filter((device) => device.online);
const onlineIds = onlineDevices.flatMap((device) =>
(device.activePhonelines ?? []).map((phoneline) => phoneline.id),
);
const onlineNumbers = onlineIds.flatMap((id) =>
numbers
.filter((number) => number.endpointId === id)
.map((number) => number.number),
);
return [...new Set(onlineNumbers)]; // remove duplicate numbers
}
async function main() {
...
const numbersModule = createNumbersModule(client);
const authenticatedWebuser = await client.getAuthenticatedWebuserId();
const numbers = (await numbersModule.getAllNumbers()).items.filter(
(number) => number.number !== centralPhone,
);
...
webhookServer.onNewCall(async (newCallEvent) => {
const onlineNumbers = await getOnlineNumbers(
client,
authenticatedWebuser,
numbers.items,
);
return respondToNewCall(newCallEvent, centralPhone, db, onlineNumbers);
});
}
logic.ts
Jetzt beginnt die Magie🔮🪄 Sobald ein:e Kund:in Ihre Hotline centralPhone anruft, wird die Funktion respondToNewCall ausgelöst:
@Entity()
webhookServer.onNewCall(async (newCallEvent) =>
respondToNewCall(newCallEvent, centralPhone, db, serviceTeamNumbers),
);
Diese Funktion bestimmt die redirectNumber mithilfe der
getRedirectNumber-Funktion, leitet den Anruf weiter und spielt dabei eine Audiodatei ab.
Die Logik des intelligenten Routings ist in der Funktion getRedirectNumber versteckt. Die Funktion iteriert über alle serviceTeamNumbers und bestimmt dabei das servicePhone mit den meisten Verbindungen zur anrufenden Person. Dafür stellen wir eine Anfrage an die Datenbank mit der Funktion acceptedCallsCount:
export async function getRedirectNumber(
customerPhone: string,
database: DataSource,
serviceTeamNumbers: string[],
): Promise {
let redirectNumber = '';
let maxAcceptedCalls = 0;
let totalAcceptedCalls = 0;
/* eslint-disable no-await-in-loop */
/* eslint-disable no-restricted-syntax */
for (const servicePhone of serviceTeamNumbers) {
const acceptedCalls = await acceptedCallsCount(
database,
customerPhone,
servicePhone,
);
if (acceptedCalls > maxAcceptedCalls) {
maxAcceptedCalls = acceptedCalls;
redirectNumber = servicePhone;
}
totalAcceptedCalls += acceptedCalls;
console.log(
`Service Phone ${servicePhone} has accepted ${acceptedCalls} call(s) by ${customerPhone}`,
);
}
if (checkRedirectThreshold(maxAcceptedCalls, totalAcceptedCalls)) {
console.log(`Redirecting to ${redirectNumber}`);
return redirectNumber;
}
console.log('Random redirect');
return serviceTeamNumbers[getRandomIntInRange(serviceTeamNumbers.length)];
}
Danach überprüfen wir mit der Funktion checkRedirectThreshold , ob die vom Routing ausgewählte Servicenummer den Schwellwert von 60% übersteigt. Hierbei definieren wir eine präferierte Servicenummer durch den Faktor 0.6 als Schwellwert, der aber beliebig festgelegt werden kann.
function checkRedirectThreshold(
maxAcceptedCalls: number,
totalAcceptedCalls: number,
): boolean {
return maxAcceptedCalls > FACTOR * totalAcceptedCalls;
}
Hier gibt es zwei mögliche Szenarien: falls eine präferierte Servicenummer gespottet wird, wird der Anruf dorthin weitergeleitet. Im anderen Fall wird eine zufällige Servicenummer zugeteilt.
Sobald der Anruf angenommen wird, löst die Push-API ein Answer-Event aus. Als Reaktion darauf speichern wir diesen angenommen Anruf in unserer Datenbank ab.
export async function respondToOnAnswer(
newAnswerEvent: AnswerEvent,
centralPhone: string,
db: DataSource,
) {
// ignore answerEvents from central phone
if (newAnswerEvent.from !== centralPhone) {
console.log(
`New answer: ${newAnswerEvent.from} by ${newAnswerEvent.answeringNumber}`,
);
await createHistoryEntry(db, newAnswerEvent);
}
}
Fazit
In diesem Tutorial haben wir eine intelligente Anrufweiterleitung mithilfe der sipgate.io Node-Library erstellt. Sie können unser Beispiel gerne als Grundlage für Ihre Projekte verwenden und an Ihre Ziele beliebig anpassen.
Wenn Sie mehr über die Möglichkeiten unserer sipgate.io-Library erfahren möchten, schauen Sie sich die anderen Tutorials in unserem Blog an.
Keine Kommentare