DEV Community

Explorer
Explorer

Posted 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)