Gruppen-SMS mit Twilio und Java

August 20, 2021
Autor:in:
Prüfer:in:

Gruppen-SMS mit Twilio und Java

Hallo und Danke fürs Lesen! Dieser Blogpost ist eine Übersetzung von Group SMS with Twilio and Java. Während wir unsere Übersetzungsprozesse verbessern, würden wir uns über Dein Feedback an help@twilio.com freuen, solltest Du etwas bemerken, was falsch übersetzt wurde. Wir bedanken uns für hilfreiche Beiträge mit Twilio Swag :)

Das Leben ist kompliziert genug. Da wollen wir nicht noch Nachrichten zwischen Freunden und Familienmitgliedern weiterleiten müssen, damit auch alle immer auf dem Laufenden sind. In diesem Beitrag erläutere ich, wie ich eine Twilio-Nummer eingerichtet habe, die ich als „meine“ Telefonnummer an die Schulen meiner Kinder weitergab. Ich wollte, dass alle Nachrichten, die an und von dieser Nummer gesendet werden, automatisch an mich und meine Frau weitergeleitet werden und einer von uns antworten kann. Bestimmt kannst du dir Situationen in deinem eigenen Leben vorstellen, in denen das praktisch sein könnte: Paketlieferungen, Party-Planung, Terminerinnerungen – die Liste ist lang.

In diesem Beitrag verwende ich Java. Der gleiche Ansatz würde jedoch in jeder anderen Sprache funktionieren, in der du eine Web-App erstellen kannst. Wenn du dich mit JavaScript auskennst, dann wäre der Beitrag SMS-Weiterleitung an mehrere Nummern auf CodeExchangeein guter Ausgangspunkt.

Was entwickeln wir?

Die Grundlage bilden eine einzige Twilio-Telefonnummer und eine Gruppe aus deiner Person und deinen Familienmitgliedern oder Freunden, die Handys haben. Du kannst die Twilio-Nummer anderen geben, als ob es deine eigene Nummer wäre.

Wir richten die Twilio-Nummer so ein, dass Nachrichten an alle Personen in deiner Gruppe weitergeleitet werden. In diesem Beispiel arbeite ich mit zwei Gruppenmitgliedern, der Code ist jedoch so konzipiert, dass du eine beliebige Zahl verwenden kannst.

Diagramm eines Telefons, das eine SMS an eine Twilio-Nummer sendet, die an zwei andere Telefone weitergeleitet wird

Wenn jemand von außerhalb deiner Gruppe (linkes Handy) eine SMS an die Twilio-Nummer sendet, wird die Nachricht an alle in der Gruppe weitergeleitet (Handys auf der rechten Seite). Die Nachrichten scheinen von der Twilio-Nummer gesendet worden zu sein, daher wird die Telefonnummer des tatsächlichen Absenders am Beginn der Nachricht hinzugefügt.

Wenn jemand in deiner Gruppe eine Antwort an die Twilio-Nummer sendet, sollte er die tatsächliche Zielnummer an den Anfang der Nachricht setzen. Sie wird entfernt, bevor die Nachricht weitergeleitet wird. Jeder in deiner Gruppe erhält eine Kopie der Nachricht:

Diagramm eines der Telefone aus dem vorherigen Diagramm, das auf die SMS antwortet. Die Nachricht wird an alle Teilnehmer gesendet.

Wenn du eine Nachricht von einem Gruppenmitglied an alle anderen senden möchtest, kannst du dies tun, indem du der Nachricht deine eigene Nummer voranstellst.

Voraussetzungen

Für diese Aufgabe benötigen wir Folgendes:

Verwenden von Twilio Programmable Messaging

Um Twilio mitzuteilen, welches Verhalten als Reaktion auf eingehende SMS gefordert wird, verwenden wir WebHooks. Wenn eine Nachricht bei unserer Telefonnummer eingeht, sendet Twilio eine HTTP-Anfrage an eine von uns angegebene URL. Wir erstellen eine App zum Senden von Anweisungen in der HTTP-Antwort, um Twilio mitzuteilen, was als Nächstes zu tun ist.

Das gleiche Diagramm wie oben, ergänzt um eine HTTP-Anfrage/Antwort von Twilio an „deine App“

Die Anweisungen in der HTTP-Antwort sind in TwiMLgeschrieben, einschließlich mehrerer Nachrichten-Tags, die Twilio anweisen, neue Textnachrichten zu senden. Inhalt und Ziel dieser Nachrichten hängen davon ab, wer die eingehende Nachricht gesendet hat und was mitgeteilt wurde. Attribute sind Teil der HTTP-Anfrage. Lies weiter, um zu erfahren, wie diese App mit Java und Spring Boot erstellt wird.

Erstellen der App

Spring Boot ist das beliebteste Framework für die Erstellung von Web-Apps in Java. Ich beginne neue Projekte gern mit dem Spring Initializr. Wenn du der Kodierung folgen möchtest, findest du unter diesem Link die gleichen Optionen, die ich verwendet habe. Du kannst auch das fertige Projekt auf GitHub suchen.

Lade das generierte Projekt herunter, entpacke es und öffne es in deiner IDE. Eine einzelne Klasse findest du in src/main/java im Paket  com.example.smsgrouproupBroadcast. Sie heißt SmsGroupBroadcastApplication. Du musst diese Klasse nicht bearbeiten, sie verfügt jedoch über eine main-Methode, die zum Ausführen der App verwendet werden kann.

Erstelle im gleichen Paket eine neue Klasse namens SmsHandler in einer neuen Datei namens SSmsHandler.java. Damit die Angelegenheit nicht zu kompliziert wird, legen wir unseren gesamten Code in dieser Klasse ab. Wenn wir fertig sind, wird er etwa 100 Zeilen umfassen.

Beginne mit dem Code, den wir beim Start ausführen müssen:

@RestController
public class SmsHandler {

   private final Set<String> groupPhoneNumbers;

   public SmsHandler() {
       groupPhoneNumbers = Set.of(System.getenv("GROUP_PHONE_NUMBERS").split(","));
   }

// more code will go in here

}

[dieser Code, einschließlich Importe, auf GitHub]

Die Annotation @RestController teilt Spring mit, dass diese Klasse nach Methoden durchsucht werden sollte, die HTTP-Anfragen verarbeiten können. In Kürze kommen wir zum Schreiben.

Die Set<String> groupPhoneNumbers in Zeile 4 wird im Konstruktor initialisiert, indem eine Umgebungsvariable gelesen wird, deren Wert eine durch Kommas getrennte Zeichenfolge von Telefonnummern im E.164-Format ist. Diese Nummern sollten deine Handynummer und die Telefonnummern aller anderen Personen in deiner Gruppe sein (alle auf der rechten Seite der Diagramme oben). Du kannst Umgebungsvariablen direkt in deiner IDE festlegen:

Screenshot, der zeigt, wie Umgebungsvariablen in IntelliJ IDEA festgelegt werden

Konfiguration der Umgebungsvariable IntelliJ IDEA

Eine Methode zur Verarbeitung von HTTP-Anfragen

Um das Schreiben der richtigen TwiML zu vereinfachen, verwenden wir die Twilio-Java-Hilfebibliothek. Füge das folgende Snippet in den Abschnitt <dependencies> von pom.xml ein, die Maven-Konfigurationsdatei, die sich auf der obersten Ebene deines Projekts befindet:

<dependency>
  <groupId>com.twilio.sdk</groupId>
  <artifactId>twilio</artifactId>
  <version>8.18.0</version>
</dependency>

[dieser Code im Kontext des Projekts auf GitHub]

Wir empfehlen, immer die neueste Version der Twilio-Hilfebibliothek zu verwenden. Zum Zeitpunkt des Schreibens ist die neueste Version 8.18.0. Unter mvnrepository.com kannst du nach neueren Versionen suchen.

Möglicherweise musst du deine IDE an dieser Stelle anweisen, die Maven-Änderungen neu zu laden. Füge dann diesen Code deiner Klasse SmsHandler hinzu:

@RequestMapping(
    value = "/sms",
    method = {RequestMethod.GET, RequestMethod.POST},
    produces = "application/xml")
@ResponseBody
public String handleSmsWebhook(
        @RequestParam("From") String fromNumber,
        @RequestParam("To")   String twilioNumber,
        @RequestParam("Body") String messageBody) {

    List<Message> outgoingMessages;

    if (groupPhoneNumbers.contains(fromNumber)) {
            outgoingMessages = messagesSentFromGroup(fromNumber, twilioNumber, messageBody);

    } else {
            outgoingMessages = messagesSentToGroup(fromNumber, twilioNumber, messageBody);
    }

    MessagingResponse.Builder responseBuilder = new MessagingResponse.Builder();
    outgoingMessages.forEach(responseBuilder::message);
    return responseBuilder.build().toXml();
}

[dieser Code, einschließlich Importe, auf GitHub]

Diese Methode beginnt mit vielen Annotationen, die von Spring erkannt werden:

  • @RequestMapping teilt Spring mit, dass diese Methode für GET- und POST-Anfragen an /sms verwendet werden sollte, und dass der Content-type in der Antwort application/xml ist.
  • @ResponseBody teilt Spring mit, dass der Rückgabewert dieser Methode als Hauptteil der HTTP-Antwort verwendet werden soll.
  • Die @RequestParam-Annotationen weisen Spring an, die benannten Parameter aus der HTTP-Anfrage zu extrahieren und als Argumente an die Methode zu übergeben. Dies funktioniert sowohl fürGET als auch für POST, obwohl sich die Parameter in verschiedenen Teilen der HTTP-Anfrage befinden.

Der Hauptteil der Methode erstellt eine Liste von Message-Objekten, die unterschiedlich gefüllt wird, je nachdem, ob die eingehende SMS, die diesen Webhook auslöste, von einem Gruppenmitglied stammt oder nicht (Zeile 12). Wir definieren die Methoden messagesSentFromGroup und messagesSentToGroup in Kürze. Beachte jedoch zuerst, wie die Liste der Messages einer MessagingResponse unter Verwendung von forEach in den Zeilen 19–21 hinzugefügt wird.

Umgang mit Nachrichten von Personen, die keine Gruppenmitglieder sind

Wenn die Methode groupPhoneNumbers.contains(fromNumber) den Wert false zurückgibt, dann wissen wir, dass die Nachricht von einer Person außerhalb unserer Gruppe stammt. In diesem Fall wird die Methode messageSentToGroup aufgerufen, um eine Liste von Message`-Objekten abzurufen, die eine Kopie der eingehenden Nachricht darstellen und an jedes Gruppenmitglied weitergeleitet werden:

private List<Message> messagesSentToGroup(String fromNumber, String twilioNumber, String messageBody) {

    List<Message> messages = new ArrayList<>();

    String finalMessage = "From " + fromNumber + " " + messageBody;
    groupPhoneNumbers.forEach(groupMemberNumber ->
                messages.add(createMessageTwiml(groupMemberNumber, twilioNumber, finalMessage))
    );

    return messages;
}

[dieser Code, einschließlich Importe, auf GitHub]

Wir erstellen die finalMessage und fügen der Liste für jedes Gruppenmitglied eine Nachricht hinzu. Ich habe eine kleine Hilfsmethode namens createMessageTwiml erstellt, um den Erbauer-Muster-Code der Twilio-Hilfebibliothek in einen Einzeiler zu verwandeln. Ich denke, der Aufwand hat sich gelohnt, da wir in diesem Kurs ein paar Mal Nachrichtenobjekte erstellen. Diese Methode sieht so aus:

private Message createMessageTwiml(String to, String from, String body) {
    return new Message.Builder()
        .to(to)
        .from(from)
        .body(new Body.Builder(body).build())
        .build();
}

[dieser Code, einschließlich Importe, auf GitHub]

Nachrichten von Gruppenmitgliedern

Wenn ein Gruppenmitglied eine Nachricht an die Twilio-Nummer sendet, sollte diese Person die tatsächliche Zielnummer voranstellen:

Eine SMS mit der Nachricht „+4477xxxx Thank you!“

Der „Thank you!“-Teil der Nachricht wird von der Twilio-Nummer an die Nummer am Anfang der Nachricht weitergeleitet. Jeder in der Gruppe erhält außerdem eine Kopie. Dazu muss die Methode messagesSentFromGroup den Nachrichtentext teilen, prüfen, ob er mit einer Telefonnummer beginnt (wenn nicht, eine hilfreiche Erinnerung zurücksenden), und eine Liste ausgehender Nachrichten erstellen, wie folgt:

private List<Message> messagesSentFromGroup(String fromNumber, String twilioNumber, String messageBody) {
    List<Message> messages = new ArrayList<>();

    String[] messageParts = messageBody.split("\\s+", 2);

    String e164Regex = "\\+[0-9]+";
    if (messageParts.length != 2 || !messageParts[0].matches(e164Regex)) {
        return List.of(createHowToMessage(fromNumber, twilioNumber));
    }

    String realToNumber = messageParts[0];
    String realMessageBody = messageParts[1];

    // add the message to the non-group recipient
    messages.add(
        createMessageTwiml(realToNumber, twilioNumber, realMessageBody)
    );

    // send a copy of the message to everyone in the group except the sender
    String groupCopyMessage = "To " + realToNumber + " " + realMessageBody;
    groupPhoneNumbers.forEach(groupMemberNumber -> {
        if (!groupMemberNumber.equals(fromNumber)) {
            messages.add(
                createMessageTwiml(groupMemberNumber, twilioNumber, groupCopyMessage));
        }
    });

    return messages;
}

private Message createHowToMessage(String fromNumber, String twilioNumber){
    return createMessageTwiml(fromNumber, twilioNumber,
        "To send a message to someone outside your group, " +
        "don't forget to include the destination phone number at the start, " +
        "eg '+44xxxx Ahoy!'");
}

[dieser Code, einschließlich Importe, auf GitHub]

Die Methode messagesSentFromGroup mag nach sehr viel Code aussehen, doch sie teilt sich ungefähr in zwei Hälften. In den Zeilen 4–9 wird die Eingabe aufgeteilt und geprüft, ob sie mit einer Telefonnummer beginnt. e164Regex überprüft auf ein +, gefolgt von Zahlen, was der E.164-Formatierung für Telefonnummern entspricht.

Der Rest von messagesSentFromGroup erstellt eine Liste aller Nachrichten, die wir senden müssen, und gibt sie zurück.

Schließlich gibt es eine separate Methode für createHowToMessage, von der ich dachte, dass sich eine Unterteilung lohnt, damit die längere Methode besser lesbar bleibt.

Code vollständig

Die SmsHandler-Klasse ist vollständig, damit ist der Code fertig. Zu Referenzzwecken ist die vollständige Klasse auf GitHub zu finden.

Lokale Ausführung des Codes

Am einfachsten startest du die App, indem du deine IDE zur Ausführung der main-Methode in der SmsGroupBroadcasterApplication-Klasse verwendest, die zuvor erwähnt wurde. Wenn du lieber ein Kommandozeilenterminal verwenden möchtest, führe ./mvnw spring-boot:run von der obersten Ebene des Projekts aus. Denke in beiden Fällen daran, die Umgebungsvariable GROUP_PHONE_NUMBERS festzulegen.

Nach dem Start der App kannst du zu http://localhost:8080/sms?From=__from__&To=__to__&Body=__body__ navigieren und solltest eine Antwort wie die folgende sehen:

<Response>
  <Message from="__to__" to="GROUP_MEMBER_1">
    <Body>From __from__ __body__</Body>
  </Message>
  <Message from="__to__" to="GROUP_MEMBER_2">
    <Body>From __from__ __body__</Body>
  </Message>
</Response>

GROUP_MEMBER_1 und GROUP_MEMBER_2 sind die Nummern in deiner Umgebungsvariable  GROUP_PHONE_NUMBERS.

Verwenden des Codes mit Twilio

Twilio benötigt eine öffentliche URL, um deine App für seine Webhooks verwenden zu können. Es gibt viele Möglichkeiten, Java-Code online bereitzustellen, doch um das Arbeiten zu vereinfachen, empfehle ich die Verwendung von ngrok.

Nach der Installation von ngrok kannst du den Befehl ngrok http 8080 ausführen. Du siehst eine https-Weiterleitungs-URL, die du für „a message comes in“ auf deiner Seite für die Konfiguration von Telefonnummern einrichten musst. Vergiss nicht, den Pfad /sms der URL hinzuzufügen:

Screenshot der Einstellung des Webhooks „When a message comes in“ in der Twilio-Konsole.

Die Festlegung der Webhook-URL für eine Telefonnummer funktioniert auch mit der Twilio CLI:

twilio phone-numbers:update <PHONE_NUMBER> --sms-url=<URL>

Wenn es sich bei der URL um eine localhost-Adresse handelt, erstellt die Tillio CLI einen ngrok-Tunnel für dich.

Wenn du testen möchtest, wie das funktioniert, bevor du die Nummer weitergibst, rekrutiere entweder ein paar Freunde mit Handys oder verwende andere Twilio-Nummern, um es auszuprobieren. Wenn du damit zufrieden bist, verschiebe die App in eine immer aktive öffentliche Cloud, damit du deinen Entwicklungscomputer nicht rund um die Uhr laufen lassen musst. Das sprengt den Rahmen dieses Beitrags, aber in der Spring-Dokumentation zu Paketierung und Bereitstellung findest du zahlreiche Optionen. Du musst nur eine HTTP-Anfrage pro SMS, die an deine Twilio-Nummer gesendet wird, bedienen. Die Anforderungen sind also sehr gering.

Mögliche weitere Schritte

Es gibt viele Verbesserungen, die du an dieser App vornehmen könntest. Beispiele:

  • Bei großen Gruppen kann es nützlich sein, die „Von“-Nummer in die Kopiernachrichten aufzunehmen.
  • Füge deiner App ein „Adressbuch“ hinzu, damit du den Namen anstelle der Nummer angeben kannst, wenn du an Empfänger sendest, die nicht Mitglieder der Gruppe sind.
  • Es gibt keine Kontrollen, mit denen sichergestellt werden kann, dass HTTP-Anfragen tatsächlich von Twilio kommen. Für diese App ist das kein großes Problem, denn wenn andere Personen deine App anrufen, wird etwas TwiML an sie zurückgegeben, was dich jedoch nichts kostet. In meinem Beitrag Sicherung von Twilio-Webhooks in Java erfährst du, wie du diese Überprüfung hinzufügen kannst.

Was du auch immer mit Twilio entwickelst, ich würde mich freuen, davon zu hören. Nimm Kontakt mit mir auf: @MaximumGilliard auf Twitter oder mgilliard@twilio.com. Viel Spaß beim Programmieren!