Build a Morse Code Application Using Go, Ngrok And Twilio

January 06, 2025
Written by
Tolulope Babatunde
Contributor
Opinions expressed by Twilio contributors are their own
Reviewed by

Build a Morse Code Application With Golang, Ngrok and Twilio

Morse Code is a way of encoding written letters as sequences of dots and dashes. It has been a dependable communication mechanism for over a century. While Morse Code is no longer commonly used in ordinary communication, developing a Morse Code application is an excellent method to improve your programming skills.

In this article, you’ll learn how to create a simple Morse Code application that converts text to Morse Code and sends it as an audio message using Twilio. Ngrok will be utilized to expose your locally running application to the internet, allowing it to receive Twilio webhooks.

Prerequisites

Before diving into the code, ensure you have the following installed:

Project overview

Below is brief step by step explanation of what will be covered in the tutorial:

  • Create a web app that can receive HTTP requests: Go will be used to build a web app that not only listens for HTTP requests, but also decodes the Morse code messages which they contain and triggers a phone call where the recipient can hear the Morse code as an audio message. To achieve this, you'll create a specific endpoint within the Go app to handle the voice requests forwarded by Twilio, allowing you to process and respond to it.
  • Configure your Twilio phone number to respond to incoming voice calls: You'll configure your Twilio phone number to respond to incoming voice calls with a webhook that directs Twilio to send requests to our Go application.
  • Create a function to convert text to Morse Code: You’ll write a Go function that takes text input and converts it into Morse Code, translating each character into its corresponding sequence of dots and dashes. Once you have the morse code, you’ll utilize TwiML to generate audio tones for the Morse Code, enabling the application to play back the Morse Code as sound during voice interactions.

Set up the project

First, create a new directory for your project, change into it, and initialize a new Go module inside it, by running the following commands:

mkdir morse-twilio
cd morse-twilio
go mod init morse-twilio

After initializing the project, you then need to install the required dependencies required for this project:

  • github.com/joho/godotenv: This dependency is used to load variables from a .env file as environment variables, accessible in your Go application. Environment variables are typically used to store information such as API keys, authentication tokens, and configuration settings, keeping them out of your code.
  • github.com/twilio/twilio-go: This is the official Twilio Go Helper Library, which provides a set of tools and functions to simplify interacting with Twilio's APIs. It simplifies the process of sending and receiving SMS messages, making voice calls, and handling various communication tasks. For this project, it will be used to simplify sending SMS messages containing Morse Code, as well as for handling incoming messages or voice calls directed to your Twilio phone number.

Run the following command to install them:

go get github.com/joho/godotenv github.com/twilio/twilio-go

Download the beep sounds

You need to download the beep sound audio files for this project. They can be found via this link. Once you do that, create a folder named static and inside it create another folder called audio. This is where the downloaded sounds need to be stored.

Set up ngrok

Next, expose port 8080 on your local machine to the public internet, by running the command below.

ngrok http 8080

You should see your ngrok running like this:

setting up and running ngrok

ngrok will provide you with a public (Forwarding) URL in its terminal output. You’ll need it for one of your functions that will be created when building to help expose your local web server to the internet, so copy it and keep it handy.

Set up the environment variables

Create a .env file in the top-level directory of your project, and add the configuration below to the file:

TWILIO_ACCOUNT_SID=<<your_account_sid>>
TWILIO_AUTH_TOKEN=<<your_auth_token>>
TWILIO_PHONE_NUMBER="<<your_twilio_phone_number>>"

Then, to get your Twilio credentials, open the Twilio Console and, from the Account Info panel on the main dashboard, copy your Account SID, Auth Token, and phone number. Then, replace the <<your_account_sid>>, <<your_auth_token>>, and <<your_twilio_phone_number>> placeholders, respectively, with the details that you just copied.

Get your Twilio credentials from your console

Build the project

Now that you have everything set up, let’s start writing Go code.

Import dependencies and define Morse Code mapping

First, create a new Go file, named main.go. Then, paste the code below into the file to add the necessary imports and define the Morse Code mapping:

package main

import (
    "encoding/xml"
    "fmt"
    "html/template"
    "log"
    "net/http"
    "os"
    "strings"
    "net/url"
    "github.com/joho/godotenv"
    "github.com/twilio/twilio-go"
    twilioApi "github.com/twilio/twilio-go/rest/api/v2010"
)

var client *twilio.RestClient
var BaseURL string = "<<your ngrok url>>"
var MorseCode = map[rune]string{
    'A': ".-", 'B': "-...", 'C': "-.-.", 'D': "-..",
    'E': ".", 'F': "..-.", 'G': "--.", 'H': "....",
    'I': "..", 'J': ".---", 'K': "-.-", 'L': ".-..",
    'M': "--", 'N': "-.", 'O': "---", 'P': ".--.",
    'Q': "--.-", 'R': ".-.", 'S': "...", 'T': "-",
    'U': "..-", 'V': "...-", 'W': ".--", 'X': "-..-",
    'Y': "-.--", 'Z': "--..",
    '0': "-----", '1': ".----", '2': "..---", '3': "...--",
    '4': "....-", '5': ".....", '6': "-....", '7': "--...",
    '8': "---..", '9': "----.",
}

This code imports the necessary packages, and defines a map for Morse Code whereeach letter and number is associated with its Morse Code representation.

Replace <<your ngrok url>> with the Forwarding URL that ngrok printed to the terminal.

Define the TwiML structures

You next need to define two structsthat define the XML format for Twilio's TwiML, which you'll use to generate the audio for the Morse Code. Paste the following after the initialisation of MorseCode in main.go.

type TwiML struct {
    XMLName xml.Name `xml:"Response"`
    Play	[]Play   `xml:"Play"`
}

type Play struct {
    Digits string `xml:"Digits,attr"`
}

Implement Morse Code conversion

Now, let's add two functions to convert text to Morse Code, and Morse Code to TwiML by pasting the following at the end of main.go:

func textToMorse(text string) string {
    var result strings.Builder
    for _, char := range strings.ToUpper(text) {
        if code, ok := MorseCode[char]; ok {
            result.WriteString(code)
            result.WriteString(" ")
        } else if char == ' ' {
            result.WriteString("  ")
        }
    }
    return result.String()
}

func morseToTwiML(morse string) []twiml.Element {
   var verbList []twiml.Element
   for _, char := range morse {
       switch char {
       case '.':
           verbList = append(verbList, &twiml.VoicePlay{Url: BaseURL + "/static/audio/short-beep.wavf"})
       case '-':
           verbList = append(verbList, &twiml.VoicePlay{Url: BaseURL + "/static/audio/long-beep.wav"})
       case ' ':
           verbList = append(verbList, &twiml.VoicePause{Length: "1"})
       }
   }
   return verbList
}

The textToMorse() function converts a string to its Morse Code representation. morseToTwiML() converts Morse Code to TwiML, using short tones for dots, long tones for dashes, and pauses for spaces.

Implement the HTTP handlers and the main function

Now, you need to create three handler functions: one for the index page, one to handle form submission, and one for receiving and processing the Twilio webhook. To do that, add the following three functions at the end of main.go.

func handleIndex(w http.ResponseWriter, r *http.Request) {
    tmpl, err := template.ParseFiles("index.html")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    tmpl.Execute(w, nil)
}

func handleSubmit(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }

    message := r.FormValue("message")
    phoneNumber := r.FormValue("phone_number")
    encodedMessage := url.QueryEscape(message)
    params := &twilioApi.CreateCallParams{}
    params.SetTo(phoneNumber)
    params.SetFrom(os.Getenv("TWILIO_PHONE_NUMBER"))
    params.SetUrl(BaseURL + "/voice?message=" + encodedMessage)

    _, err := client.Api.CreateCall(params)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    fmt.Fprintf(w, "Call initiated to %s with message: %s", phoneNumber, message)
}

func handleVoiceRequest(w http.ResponseWriter, r *http.Request) {
    message := r.URL.Query().Get("message")
    if message == "" {
        message = "Hello World"
    }
    w.Header().Set("Content-Type", "application/xml")
    output, err := twiml.Voice(morseToTwiML(textToMorse(message)))
    if err != nil {
        fmt.Println(err)
        return
    }
    w.Write([]byte(output))
}

The handleIndex() function handles requests to serve the HTML form, handleSubmit() handles requests to process form submissions, and handleVoiceRequest() initiates Twilio voice calls.

Finally, let's implement the main() function to tie everything together, by pasting the following at the end of main.go:

func main() {
    err := godotenv.Load()
    if err != nil {
        log.Fatal("Error loading .env file")
    }

    client = twilio.NewRestClientWithParams(twilio.ClientParams{
        Username: os.Getenv("TWILIO_ACCOUNT_SID"),
        Password: os.Getenv("TWILIO_AUTH_TOKEN"),
    })

    mux := http.NewServeMux()
    fileServer := http.FileServer(http.Dir("./static/"))
    mux.Handle("/static/", http.StripPrefix("/static", fileServer))
    mux.HandleFunc("/", handleIndex)
    mux.HandleFunc("/submit", handleSubmit)
    mux.HandleFunc("/voice", handleVoiceRequest)

    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }
    fmt.Printf("Server is running on port %s\n", port)
    log.Fatal(http.ListenAndServe(":"+port, mux))
}

Create the HTML form

Create a file named index.html in the top-level directory of your project with the following content:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Morse Code Caller</title>
</head>
<body>
    <h1>Morse Code Caller</h1>
    <form action="/submit" method="post">
        <label for="message">Message:</label><br>
        <input type="text" id="message" name="message" required><br>
        <label for="phone_number">Phone Number:</label><br>
        <input type="tel" id="phone_number" name="phone_number" required><br>
        <input type="submit" value="Send Morse Code">
    </form>
</body>
</html>

This is just a basic HTML form that allows the users to input a text message and a phone number. When the form is submitted to the "/submit" POST route, the provided message and phone number will be processed by the server to initiate a phone call that transmits the message in Morse code.

Run the application

You already started ngrok earlier in the tutorial, so you just need to run the Go code now. To do so, run the following command:

go run main.go

Now, open http://localhost:8080/, where you'll see something it render like the screenshot below:

Image displaying the morse code caller view

Fill out the form and submit it. You'll see "Call initiated to <the phone supplied number> with message: Here is a message" output to the browser, and then the app will generate a Morse Code audio file for the message that you wrote and send it to the specified phone number.

That’s how to build a Morse Code application in Go

In this article, you built a web application that converts text to Morse Code and initiates a phone call to play it. Used Go for the backend, Twilio for making calls, and created a simple HTML form for the user interface. Ngrok was also used to make your local server accessible to Twilio.

This project shows how flexible twilio can be for creating anything! It also demonstrates how to work with web servers in Go, interact with external APIs, generate audio output through phone calls, and use tunneling services for local development.

Tolulope Babatunde is a software developer with a passion for translating complex concepts in clear content through tech writing.

Morse code icons created by photo3idea_studio - Flaticon.