How to locally test your Webhooks using Java

13 min read
Tags:

Webhooks is ideal in situations where you need to get notified of certain events like an email being opened, a contact getting updated or a calendar event getting deleted. While you need an external server to host your webhook-enabled application, thanks to the Nylas Java SDK, you can easily test webhooks locally on your machine.

What are we going to talk about?

Ways to test Webhooks

As we know, we can use a web server to test our webhooks as we demonstrated using Ruby, Python, and PHP , we can test webhooks locally by using the Nylas CLI.

In Episode 26 of Coding with Nylas, Exploring Nylas’ webhook utilities we saw a preview of how to use the NodeJS SDK to test webhooks locally.

This of course brings us to our main point of showing how to use another one of our SDKs to test webhooks locally, the Java SDK.

Why test Webhooks locally

Well, anything that we can test on our local machine is better, because if anything goes wrong, we can always quickly fix it. Having to test on a web server makes it more complex as we need sometimes to repeat a long workflow to submit all the needed changes.

By testing locally, we can make sure everything is working properly before we move it to an external web server.

What does our webhooks application look like?

We’re going to create a SparkJava application, which is a web application but also we’re going to use Twilio to be able to send ourselves an SMS message when a certain event happens, like when someone opens a link that we sent.

Local webhooks in Java

Let’s zoom in to showcase that we’re getting information on who’s sending the email, who is receiving the email, and what type or kind of Webhook we’re getting.

Local webhooks in Java upclose

When the recipient opens the link that we send inside an email, we will get notified via SMS:

Twilio SMS

Let’s recap the information we’re getting:

  • Id → The message id. This will help us to retrieve the message information.
  • Date → Date and time when the webhook notification gets generated.
  • Email From → Who is sending the message
  • Email To → Who is receiving the message
  • Title → Subject of the message
  • Type → Type of Webhook, created, updated or opened.

Is your system ready?

If you already have the Nylas Java SDK installed and your Java environment is configured, then continue along with the blog.

Otherwise, I would recommend that you read the post How to Send Emails with the Nylas Java SDK where the basic setup is clearly explained.

Creating a Twilio account

In order to get SMS delivered to our cell phones, we need to create a Twilio account:

Signing up for Twilio

After that, you will need to create a Twilio Phone number from where the SMS will be sent:

Creating your Twilio account

Don’t forget to copy the Account SID, Auth Token and Twilio phone number and store them in your .env file:

Setting up your Twilio token

Creating an email tracking application

Our Webhooks application is going to deal with two different kinds of emails, one tracked and the other untracked. The first one needs to be sent using some extra commands, so we’re going to create a small program for that, the second one can be sent like any regular email.

Let’s create a new project called Email_Tracking with a main class called emailtracking.java.

Keep in mind that Email Tracking is a paid customer feature, so with a trail account, you will not be able to use it.

Let’s start with the pom.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>Nylas</groupId>
    <artifactId>Email_Tracking</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>18</maven.compiler.source>
        <maven.compiler.target>18</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.nylas.sdk</groupId>
            <artifactId>nylas-java-sdk</artifactId>
            <version>1.21.0</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>2.0.6</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>2.0.6</version>
        </dependency>
        <dependency>
            <groupId>io.github.cdimascio</groupId>
            <artifactId>dotenv-java</artifactId>
            <version>2.3.2</version>
        </dependency>
    </dependencies>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <!-- Build an executable JAR -->
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-jar-plugin</artifactId>
                    <version>3.1.0</version>
                    <configuration>
                        <archive>
                            <manifest>
                                <addClasspath>true</addClasspath>
                                <mainClass>emailtracking</mainClass>
                            </manifest>
                        </archive>
                    </configuration>
                </plugin>
                <plugin>
                    <artifactId>maven-assembly-plugin</artifactId>
                    <configuration>
                        <archive>
                            <manifest>
                                <mainClass>emailtracking</mainClass>
                            </manifest>
                        </archive>
                        <descriptorRefs>
                            <descriptorRef>jar-with-dependencies</descriptorRef>
                        </descriptorRefs>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>exec-maven-plugin</artifactId>
                    <version>1.2.1</version>
                    <executions>
                        <execution>
                            <phase>package</phase>
                            <goals>
                                <goal>java</goal>
                            </goals>
                        </execution>
                    </executions>
                    <configuration>
                        <mainClass>emailtracking</mainClass>
                        <cleanupDaemonThreads>false</cleanupDaemonThreads>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

And then the emailtracking.java file:

// Import Java Utilities
import java.util.Arrays;

// Import Nylas Packages
import com.nylas.NylasClient;
import com.nylas.NylasAccount;
import com.nylas.Tracking;
import com.nylas.Draft;
import com.nylas.NameEmail;

//Import DotEnv to handle .env files
import io.github.cdimascio.dotenv.Dotenv;

public class emailtracking {
    public static void main(String[] args) throws Exception {
        Dotenv dotenv = Dotenv.load();
        // Create the Nylas client object
        NylasClient client = new NylasClient();
        NylasAccount account = client.account(dotenv.get("ACCESS_TOKEN"));

        // Create the draft email
        Draft draft = new Draft();
        draft.setSubject("With Love, From Nylas");
        draft.setTo(Arrays.asList(new NameEmail("YourName", "YourEmail")));
        draft.setBody("This email was sent using the Nylas email API. 
                                Visit <a href='https://nylas.com'>Nylas</a> for details.");

        // Create the tracking object and add it to the draft
        Tracking tracking = new Tracking();
        tracking.setOpens(true);
        tracking.setLinks(true);
        tracking.setThreadReplies(true);
        tracking.setPayload("payload");
        draft.setTracking(tracking);

        // Send the draft
        account.drafts().send(draft);
    }
}

Let’s compile and run our application:

$ mvn package && mvn exec:java -Dexec.mainClass="emailtracking"
Running our email tracking application

We should get an email in our mailbox:

Checking our inbox

Creating our Java Webhooks project

Our project is going to be called Local_Webhooks, and it will have a main class called localwebhooks. We will need to change the default pom.xml file and create one Mustache template called main.

As we mentioned, we want to use a web framework, and a very light and fast option is SparkJava.

Creating the pom.xml file

We’re going to include some useful libraries:

  • spark-core → The Spark Web Framework
  • nylas-java-sdk → The Nylas Java SDK
  • dotenv-java → To enable using .env files
  • spark-template-mustache → To allow mustache on Spark projects
  • twilio → To deal with SMS
  • slf4j-api → To handle loggingslf4j-simple → To handle logging
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>Nylas</groupId>
    <artifactId>Local_Webhooks</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>18</maven.compiler.source>
        <maven.compiler.target>18</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.nylas.sdk</groupId>
            <artifactId>nylas-java-sdk</artifactId>
            <version>1.21.0</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>2.0.6</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>2.0.6</version>
        </dependency>
        <dependency>
            <groupId>io.github.cdimascio</groupId>
            <artifactId>dotenv-java</artifactId>
            <version>2.3.2</version>
        </dependency>
        <dependency>
            <groupId>com.sparkjava</groupId>
            <artifactId>spark-core</artifactId>
            <version>2.9.4</version>
        </dependency>
        <dependency>
            <groupId>com.sparkjava</groupId>
            <artifactId>spark-template-mustache</artifactId>
            <version>2.7.1</version>
        </dependency>
        <dependency>
            <groupId>com.twilio.sdk</groupId>
            <artifactId>twilio</artifactId>
            <version>9.2.5</version>
        </dependency>
    </dependencies>
</project>

With our pom.xml file ready, we can move on to the source code.

Creating the source code

Here’s the source code of our localwebhooks.java class:

//Import Java Utilities
import java.net.URISyntaxException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.io.IOException;

//Import Nylas Packages
import com.nylas.*;
import com.nylas.services.Tunnel;

//Import DotEnv to handle .env files
import io.github.cdimascio.dotenv.Dotenv;

// Import Spark and Mustache libraries
import spark.ModelAndView;
import static spark.Spark.*;
import spark.template.mustache.MustacheTemplateEngine;

// Import Twilio libraries
import com.twilio.Twilio;
import com.twilio.rest.api.v2010.account.Message;

public class localwebhooks {
    static Dotenv dotenv = Dotenv.load();
    private static String id;
    private static String type;

    // Get Twilio keys
    public static final String ACCOUNT_SID = dotenv.get("TWILIO_SID");
    public static final String AUTH_TOKEN = dotenv.get("TWILIO_TOKEN");
    public static void main(String[] args) throws RequestFailedException, 
                                                                          IOException, URISyntaxException {
        // Initialize Twilio client
        Twilio.init(ACCOUNT_SID, AUTH_TOKEN);
        // List to hold webhooks notifications
        List<WebhookInfo> webhook_list = new ArrayList<>();
        // Create the Nylas client object
        NylasClient client = new NylasClient();
        // Create the Nylas account object
        NylasAccount account = client.account(dotenv.get("ACCESS_TOKEN"));
        // Connect it to Nylas using the Access Token from the .env file
        NylasApplication application = client.application(dotenv.get("CLIENT_ID"),
                                                                                     dotenv.get("CLIENT_SECRET"));
        // Create a Webhook tunnel connection, passing the required triggers
        Tunnel webhookTunnel = new Tunnel.Builder(application, new HandleNotifications()).
                triggers(Webhook.Trigger.MessageCreated,
                         Webhook.Trigger.MessageOpened,
                         Webhook.Trigger.MessageUpdated,
                         Webhook.Trigger.MessageLinkClicked).build();
        // Establish the Webhook connection
        webhookTunnel.connect();

        // Default path when we load our web application
        get("/", (request, response) -> {
            // Create the Nylas messages endpoint
            Messages messages = account.messages();
            // If we get the message id
            if(id != "" && id != null){
                // Create a new webhookinfo structure object
                WebhookInfo info = new WebhookInfo();
                // Retrive the information from the message
                com.nylas.Message message = messages.get(id);
                // Convert the date into something readable
                LocalDateTime ldt = LocalDateTime.ofInstant(message.getDate(),
                                                 ZoneOffset.UTC);
                String date = ldt.getYear() + "/" + ldt.getMonthValue() + "/" +
                                     ldt.getDayOfMonth();
                String time = ldt.getHour() + ":" + ldt.getMinute() + ":" + ldt.getSecond();
                // Fill up the webhookinfo structure
                info.id = message.getId();
                info.date = date + " on " + time;
                info.emailFrom = message.getFrom().get(0).getEmail();
                info.emailTo = message.getTo().get(0).getEmail();
                info.title = message.getSubject();
                info.type = type;
                // Add the structure to the list of webhooks notifications
                webhook_list.add(info);
                // If someone click on the link in the email
                if(type.equals("message.link_clicked")){
                    // Send a Twilio SMS
                    Message twilio_message = Message.creator(
                            new com.twilio.type.PhoneNumber("+13439969736"),
                            new com.twilio.type.PhoneNumber(dotenv.get("TWILIO_PHONE")),
                            "The link you sent to " + message.getTo().get(0).getEmail() + 
                           " was opened").create();
                    // Trigger the SMS
                    System.out.println(twilio_message.getSid());
                }
            }
            id = "";
            // Create a model to pass information to the mustache template
            Map<String, Object> model = new HashMap<>();
            model.put("webhook_list", webhook_list);
            // Call the mustache template
            return new ModelAndView(model, "main.mustache");
        }, new MustacheTemplateEngine());
    }


    // Setters and Getters for Id and Type
    public static void set_Id(String sId){
        id = sId;
    }

    public static String get_Id(){
        return id;
    }

    public static void set_Type(String sType){
        type = sType;
    }

    public static String get_Type(){
        return type;
    }
}

// Structure to hold the Webhooks notification information
class WebhookInfo {
    String id;
    String date;
    String emailFrom;
    String emailTo;
    String title;
    String type;
}

// Every time there's a new Webhook, we're going to deal with it here
class HandleNotifications implements Tunnel.WebhookHandler {
    @Override
    public void onMessage(Notification.Delta delta) {
        Dotenv dotenv = Dotenv.load();
        localwebhooks local = new localwebhooks();
        String type = delta.getTrigger();
        // Get the Id for either message.created or message.updated
        if(type.equals("message.created") || type.equals("message.updated")) {
            local.set_Id(delta.getObjectData().getId());
        // Get the Id for either message.opened or message.link_clicked
        }else{
            local.set_Id(delta.getObjectData().getMessageTrackingData().getMessageId());
        }
        local.set_Type(type);
    }

    // When the connection to the Webhook tunnel is made
    @Override
    public void onOpen(short httpStatusCode) {
        System.out.println("Webhook tunnel is open");
    }
}

Inside the resources folder, we need to create a templates folder and inside we need to create one file called main.mustache:

<html>
<head>
    <script src="https://cdn.tailwindcss.com"></script>
    <title>Webhooks Monitor</title>
    <meta http-equiv="refresh" content="5">
    <style>
        td {
            text-align: center;
        }
    </style>
</head>
<body>
<div class="bg-green-600 border-green-600 border-b p-4 m-4 rounded grid place-items-center">
    <p class="text-6xl text-center">Webhooks Monitor</p>
    <br>
    <table border="1" style="width:100%">
        <tr>
            <th>Id</th>
            <th>Date</th>
            <th>Email From</th>
            <th>Email To</th>
            <th>Title</th>
            <th>Type</th>
        </tr>
        {{#webhook_list}}
            <tr>
                <td><p class="text-white"><b>{{id}}</b></p></td>
                <td><p class="text-white"><b>{{date}}</b></p></td>
                <td><p class="text-white"><b>{{emailFrom}}</b></p></td>
                <td><p class="text-white"><b>{{emailTo}}</b></p></td>
                <td><p class="text-white"><b>{{title}}</b></p></td>
                <td><p class="text-white"><b>{{type}}</b></p></td>
            </tr>
        {{/webhook_list}}
    </table>
</div>
</body>
</html>

And that’s it. We’re ready to roll.

Running our Webhooks project

In order to run our application, we need to compile it by typing the following on the terminal window:

$ mvn package
mvn package

To actually run the application, we need to type the following:

$ mvn exec:java -Dexec.mainClass="localwebhooks"
Running our local Java Webhooks application
Local Server details

Our application will be running on port 4567 of localhost, so we just need to open our favourite browser and go to the following address:

http://localhost:4567

What’s next?

Locally testing your webhooks with Java saves you a lot of time and helps you to prevent errors.

If you want to learn more about Webhooks, go to our documentation.

You can sign up Nylas for free and start building!

Don’t miss the action, join our LiveStream Coding with Nylas:

Related resources

How to create an appointment scheduler in your React app

Learn how to create an appointment scheduler in your React app using Nylas Scheduler. Streamline bookings and UX with step-by-step guidance.

Beyond APIs: Designing elegant, smart Web Components

Dive into the world of smart Web Components for advanced API integration solutions. Design elegant, customizable elements to elevate user experiences.

How to integrate a React calendar component to streamline scheduling

Elevate your app’s scheduling with a React calendar component and seamlessly integrate with Nylas for efficient calendar management.