Run Without Docker¶
This guide explains how to run the five Spring Boot applications directly on a local computer or virtual machine (VM). PostgreSQL, Redis, and Kafka must run on the same host or on servers the applications can reach. For production, use a service manager such as systemd. The commands below run in the foreground and are intended for development and testing.
1. Install the required software¶
Install:
- JDK 17. Check it with
java -versionandjavac -version. - PostgreSQL. The Compose setup uses version 16.
- A server compatible with Redis 7.
- A Kafka broker using KRaft or ZooKeeper. Every service host must be able to connect to the address published by Kafka.
curlto check service health.
You do not need to install Maven. The Maven wrapper downloads Maven 3.8.6. Run it with sh mvnw, or run chmod +x mvnw once and then use ./mvnw.
2. Prepare PostgreSQL¶
Create one database and three schemas. For local development, use one database user for all three schemas. This allows the aggregator to access the router tables it needs.
CREATE ROLE appuser LOGIN PASSWORD 'replace-with-a-strong-password';
CREATE DATABASE sms_gateway OWNER appuser;
\connect sms_gateway
CREATE SCHEMA IF NOT EXISTS client_service AUTHORIZATION appuser;
CREATE SCHEMA IF NOT EXISTS sms_router AUTHORIZATION appuser;
CREATE SCHEMA IF NOT EXISTS sms_aggregator AUTHORIZATION appuser;
Run these statements as a PostgreSQL administrator. You can also create the sms_gateway database, connect to it as its owner, and run scripts/init-db.sql.
Each application uses Flyway to update its schema. Start the router before the aggregator because the aggregator checks and uses selected router tables. In production, use separate database users. Give the aggregator access only to the sms_router tables and sequences it needs.
3. Prepare Redis and Kafka¶
Start Redis on localhost:6379. The client service and gateway must use the same Redis database, which is 5 by default. The gateway reads sessions created by the client service.
Start Kafka on localhost:9092. Make sure every service host can connect to the address Kafka publishes. Kafka can create topics automatically during development. In production, create these topics in advance and set suitable partition counts:
sms.reportingsms.reporting.dltclient.channel-alertswhen webhook alert delivery is not set upsms.outbound.{mnoUserId}topics and their matching dead-letter topics
The aggregator finds outbound topics by their name pattern. A new topic may not be assigned to the aggregator until Kafka refreshes its topic information.
4. Build executable JARs¶
From the repository root:
If you made the wrapper executable, you can use ./mvnw clean package instead.
On Windows:
Use -DskipTests only if your deployment process has already run and passed the tests.
The build creates these files:
| Service | JAR path |
|---|---|
| Discovery | services/discovery-service/service-jars/discovery-service.jar |
| Client | services/all-service-jars/client-service.jar |
| SMS router | services/all-service-jars/sms-router-service.jar |
| Aggregator integration | services/all-service-jars/aggregator-integration-service.jar |
| Gateway | services/gateway-service/service-jars/gateway-service.jar |
Check that all files exist before starting the services:
ls -l \
services/discovery-service/service-jars/discovery-service.jar \
services/all-service-jars/client-service.jar \
services/all-service-jars/sms-router-service.jar \
services/all-service-jars/aggregator-integration-service.jar \
services/gateway-service/service-jars/gateway-service.jar
5. Generate shared secrets¶
Generate each secret once. Services that share a secret must receive the same value:
openssl rand -base64 64 # JWT_SECRET_KEY: client + gateway
openssl rand -hex 32 # ADMIN_API_KEY: client + router + aggregator
openssl rand -base64 32 # JASYPT_ENCRYPTOR_PASSWORD: router + aggregator
JWT_SECRET_KEY must be a Base64 string because its decoded bytes are used to sign HS512 tokens. Do not use a normal sentence. Store these secrets in a secret manager or a protected environment file used by your service manager. Do not save them in shell history or source control.
The commands below contain placeholders. Replace each shared placeholder with the same value in every service that uses it.
6. Start the discovery service¶
In the first terminal or service unit, run:
Check the service on port 8761:
The discovery service does not use PostgreSQL, Redis, or Kafka.
7. Start the client service¶
In the second terminal or service unit, run:
export SPRING_PROFILES_ACTIVE=dev
export EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE='http://localhost:8761/eureka/'
export DB_URL='jdbc:postgresql://localhost:5432/sms_gateway?currentSchema=client_service'
export DB_SLAVE_URL="$DB_URL"
export DB_USERNAME=appuser
export DB_PASSWORD='<database-password>'
export REDIS_HOST=localhost
export REDIS_PORT=6379
export REDIS_PASSWORD=''
export REDIS_DATABASE_INDEX=5
export KAFKA_BOOTSTRAP_SERVERS='localhost:9092'
export JWT_SECRET_KEY='<base64-jwt-secret>'
export JWT_EXPIRY_MS=3600000
export JWT_REFRESH_EXPIRY_MS=604800000
export ADMIN_API_KEY='<admin-key-at-least-32-characters>'
java -jar services/all-service-jars/client-service.jar
The development profile uses port 9014. Check that the database update completed and the service is ready:
You can leave out DB_SLAVE_URL; the service will use DB_URL instead. If you use a read replica, recent authentication, configuration, or report changes may take time to appear because of replication delay.
8. Start the SMS router¶
In the third terminal or service unit, use the same admin key as the client service. Also set the Jasypt password that the aggregator will use:
export ACTIVE_PROFILE=dev
export EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE='http://localhost:8761/eureka/'
export DB_URL='jdbc:postgresql://localhost:5432/sms_gateway?currentSchema=sms_router'
export DB_USERNAME=appuser
export DB_PASSWORD='<database-password>'
export KAFKA_BOOTSTRAP_SERVERS='localhost:9092'
export ADMIN_API_KEY='<same-admin-key>'
export JASYPT_ENCRYPTOR_PASSWORD='<shared-jasypt-master-key>'
export BTRC_ENABLED=true
export BTRC_ZONE_ID=Asia/Dhaka
export BTRC_WINDOW_START_HOUR=9
export BTRC_WINDOW_END_HOUR=24
java -jar services/all-service-jars/sms-router-service.jar
The development profile uses port 9015. Check that the service is ready:
The router finds the client service through Eureka and uses the shared admin key for internal balance requests.
9. Start the aggregator integration service¶
In the fourth terminal or service unit, run:
export ACTIVE_PROFILE=dev
export EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE='http://localhost:8761/eureka/'
export DB_URL='jdbc:postgresql://localhost:5432/sms_gateway?currentSchema=sms_aggregator'
export DB_USERNAME=appuser
export DB_PASSWORD='<database-password>'
export KAFKA_BOOTSTRAP_SERVERS='localhost:9092'
export ADMIN_API_KEY='<same-admin-key>'
export JASYPT_ENCRYPTOR_PASSWORD='<same-jasypt-master-key>'
export MNPSP_BASE_URL='https://api.mnpspbd.com'
export MNPSP_AGGREGATOR_API_KEY='<infozillion-api-key>'
export MNPSP_MNO_URL_PATH='/a2p-sms/api/v1'
export MNPSP_IPTSP_URL_PATH='/a2p-sms-iptsp/api/v1'
export DLR_POLLER_ENABLED=true
java -jar services/all-service-jars/aggregator-integration-service.jar
The development profile uses port 9016. This service provides only health and management endpoints; it does not provide a business API.
For development, set MNPSP_BASE_URL to an approved test server instead of the production provider. The Compose end-to-end test setup uses http://mnpsp-stub:8888. When running without Docker, use the URL of a test server that the application can reach.
10. Start the gateway¶
In the fifth terminal or service unit, use the same JWT secret and Redis database as the client service:
export ACTIVE_PROFILE=dev
export SERVER_PORT=8000
export EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE='http://localhost:8761/eureka/'
export REDIS_HOST=localhost
export REDIS_PORT=6379
export REDIS_PASSWORD=''
export REDIS_DATABASE_INDEX=5
export JWT_SECRET_KEY='<same-base64-jwt-secret>'
java -jar services/gateway-service/service-jars/gateway-service.jar
Set SERVER_PORT=8000 because application-dev.yml otherwise changes the port to 8081. Send external requests to port 8000.
11. Check service registration and API routing¶
Check that all expected applications are registered with Eureka:
Check that the gateway can reach the client service. Send a GET request to the login endpoint, which accepts only POST requests. A response such as 405 Method Not Allowed shows that the request reached the client service. A gateway 503 means it did not:
Next, add the required client and routing data through the internal APIs, then test a login and SMS submission. A new installation cannot send SMS until it has:
- a client and client user;
- balance/credit;
- an MNO or IPTSP group and credentials;
- channels and sender CLIs approved for the tenant;
- billing rates for the tenant, channel, and operator;
- credentials for a provider or test server that the application can reach.
Run services in production¶
When using systemd or another service manager:
- Use a separate unit and protected environment file for each service.
- Set the startup order and configure automatic restarts with a delay after failures.
- Run each service as an operating-system user without administrator privileges.
- Set the working directory, log directory, and file permissions.
- Pass
SIGTERMto the service and allow enough time for a clean shutdown. - Use a firewall to block external access to ports 8761, 9014, 9015, and 9016.
- Put a TLS proxy or load balancer in front of gateway port 8000.
- Do not use the shell
exportexamples as permanent secret storage.
Common startup problems¶
| Symptom | Likely cause |
|---|---|
| Client fails to start | Admin key is missing or too short, the Base64 JWT secret is invalid, the database update failed, or the database password is wrong |
| Gateway does not accept a client token | The JWT secrets do not match, or the gateway and client service use different Redis hosts or databases |
Gateway returns 503 for a route |
The target service is not registered yet, the Eureka URL is wrong, or the service registered with the wrong port |
| Aggregator fails its database checks | The router database updates are incomplete, or the database user cannot access the required sms_router tables |
| Messages remain in the router outbox | Kafka is unavailable, the topic is missing, or the producer failed |
| Outbound messages are not consumed immediately | The aggregator has not found the new topic yet |
| Provider calls fail immediately | The Infozillion URL or API key is wrong, the stored credentials cannot be decrypted, or the Jasypt passwords do not match |
See the configuration index and troubleshooting guides for more help.