Intelligentes Routing mit sipgate.io

René
09.08.2022 0 9:13 min

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 DockerInstall 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
Wenn Sie Webhooks in Ihrer Live-Umgebung verwenden, sollten Sie Ihren Code auf einem geeigneten Webserver mit einer geeigneten Adresse ausführen. Für Entwicklungszwecke empfehlen wir jedoch die Nutzung eines Dienstes, der Ihre lokale Umgebung über das Internet zugänglich macht. Dies erleichtert erheblich das Testen Ihres geschriebenen Codes. Es gibt verschiedene kostenlose Dienste, die dafür genutzt werden können. Einige Beispiele sind localhost.run oder ngrok. Beide stellen Ihnen eine öffentliche URL zur Verfügung, mit der Sie Webhooks von sipgate.io empfangen können. Achten Sie darauf, dass Sie den richtigen Port weiterleiten (in diesem Tutorial Port 8080) und dass der von Ihnen gewählte Anbieter sichere Verbindungen über HTTPS bietet.
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.

   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


Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert