DEV Community

Explorer
Explorer

Posted on Edited on

Sending Automated SMS Notifications from Joget Workflows via BeanShell

While email notifications work well for standard office approvals, urgent alerts—such as appointment reminders, multi-factor OTP codes, or emergency escalations—require SMS messaging.

Joget DX enables developers to connect to any external SMS Gateway REST API (e.g., Twilio, Infobip, or local telecom providers) directly from a workflow BeanShell Tool step.

In this guide, we'll build a Java/BeanShell script that dynamically aggregates recipient phone numbers, constructs a bilingual SMS message, and dispatches a JSON POST payload to a REST SMS Gateway API.


How It Works

  1. Phone Number Sanitization & Aggregation: The script retrieves recipient phone numbers from multiple form fields using Hash Variables (#form.request.mobile_no#), strips empty entries, and formats a comma-separated list.
  2. Supplemental Data Lookup: JDBC connection queries additional appointment details (e.g., meeting location or reference status) from internal Joget database tables.
  3. HTTP REST Dispatch: Using java.net.HttpURLConnection, the script sends a UTF-8 encoded JSON POST request to the Gateway URL configured in Joget App Variables (#appVariable.sms_gateway_url#).

The BeanShell Script

Place this code inside a BeanShell Tool step within your Joget workflow:

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.OutputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.nio.charset.StandardCharsets;
import org.json.JSONObject;
import org.joget.apps.app.service.AppUtil;
import org.joget.commons.util.LogUtil;

// 1. Collect and sanitize phone numbers from form fields
String primaryMobile = "#form.appointment.primary_phone#";
String secondaryMobile = "#form.appointment.secondary_phone#";

StringBuilder mobileBuilder = new StringBuilder();
if (primaryMobile != null && !primaryMobile.trim().isEmpty()) {
    mobileBuilder.append(primaryMobile.trim());
}
if (secondaryMobile != null && !secondaryMobile.trim().isEmpty()) {
    if (mobileBuilder.length() > 0) mobileBuilder.append(",");
    mobileBuilder.append(secondaryMobile.trim());
}

String recipientMobiles = mobileBuilder.toString();
if (recipientMobiles.isEmpty()) {
    LogUtil.warn("SMS Gateway", "No valid mobile numbers provided. Skipping SMS send.");
    return;
}

// 2. Fetch supplemental data from database if needed
String processId = "#process.recordId#";
String location = "Main Headquarters";

Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;

try {
    javax.sql.DataSource ds = (javax.sql.DataSource) AppUtil.getApplicationContext().getBean("setupDataSource");
    con = ds.getConnection();

    String sql = "SELECT c_location FROM app_fd_appointment_details WHERE c_fk = ?";
    ps = con.prepareStatement(sql);
    ps.setString(1, processId);
    rs = ps.executeQuery();
    if (rs.next() && rs.getString("c_location") != null) {
        location = rs.getString("c_location");
    }
} catch (Exception e) {
    LogUtil.error("SMS Gateway", e, "Error fetching appointment location");
} finally {
    if (rs != null) try { rs.close(); } catch (Exception e) {}
    if (ps != null) try { ps.close(); } catch (Exception e) {}
    if (con != null) try { con.close(); } catch (Exception e) {}
}

// 3. Build SMS Body
String appointmentRef = "#form.appointment.ref_no#";
String appointmentDate = "#form.appointment.date#";

String messageBody = "Reminder: Your appointment (Ref: " + appointmentRef + ") is scheduled for " +
                     appointmentDate + " at " + location + ". Thank you.";

// 4. Send REST POST Payload to SMS Gateway
String smsGatewayUrl = "#appVariable.sms_gateway_endpoint#"; // e.g. https://api.sms-provider.com/v1/send
JSONObject jsonPayload = new JSONObject();
jsonPayload.put("recipients", recipientMobiles);
jsonPayload.put("message", messageBody);

try {
    URL url = new URL(smsGatewayUrl);
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setRequestMethod("POST");
    conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
    conn.setRequestProperty("Authorization", "Bearer #appVariable.sms_api_key#");
    conn.setDoOutput(true);
    conn.setConnectTimeout(5000);
    conn.setReadTimeout(5000);

    OutputStream os = conn.getOutputStream();
    os.write(jsonPayload.toString().getBytes(StandardCharsets.UTF_8));
    os.flush();
    os.close();

    int responseCode = conn.getResponseCode();
    LogUtil.info("SMS Gateway", "SMS API Response Code: " + responseCode + " for recipients: " + recipientMobiles);

    if (responseCode >= 200 && responseCode < 300) {
        BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
        String inputLine;
        StringBuilder responseStr = new StringBuilder();
        while ((inputLine = in.readLine()) != null) {
            responseStr.append(inputLine);
        }
        in.close();
        LogUtil.info("SMS Gateway", "SMS Gateway Output: " + responseStr.toString());
    } else {
        LogUtil.error("SMS Gateway", null, "Failed to dispatch SMS. HTTP Response Code: " + responseCode);
    }

} catch (Exception e) {
    LogUtil.error("SMS Gateway", e, "Error dispatching HTTP POST request to SMS Gateway");
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Timeout Configuration: Always set explicit connect (setConnectTimeout) and read timeouts (setReadTimeout) so an unresponsive gateway doesn't lock up active workflow threads.
  • Store Credentials Securely: Store API keys and endpoint URLs in App Variables (#appVariable.sms_api_key#) rather than hardcoding them in scripts.

Top comments (0)