Skip to content

Commit b55b48c

Browse files
authored
Merge pull request #394 from Ayinkx1/feat/103-achievement-service-setup
add sms service
2 parents 14fcb93 + 034ed49 commit b55b48c

63 files changed

Lines changed: 3529 additions & 871 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

eslint.config.mjs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@ export default tseslint.config(
1919
},
2020
sourceType: 'commonjs',
2121
parserOptions: {
22-
projectService: {
23-
allowDefaultProject: ['src/**/*.spec.ts', 'test/**/*.ts'],
24-
},
22+
projectService: true,
2523
tsconfigRootDir: import.meta.dirname,
2624
},
2725
},
@@ -33,4 +31,4 @@ export default tseslint.config(
3331
'@typescript-eslint/no-unsafe-argument': 'warn'
3432
},
3533
},
36-
);
34+
);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"extends": "../tsconfig.json",
3+
"compilerOptions": {
4+
"rootDir": ".."
5+
},
6+
"include": ["./**/*"],
7+
"exclude": ["node_modules", "dist"]
8+
}
Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,40 @@
11
NODE_ENV=development
2-
SERVICE_PORT=3010
2+
SERVICE_PORT=3007
3+
SERVICE_VERSION=1.0.0
34

4-
DB_HOST=localhost
5+
DB_TYPE=postgres
6+
DB_HOST=127.0.0.1
57
DB_PORT=5432
68
DB_USER=postgres
79
DB_PASSWORD=postgres
810
DB_NAME=sms_service
9-
DB_SYNCHRONIZE=true
11+
DB_SYNC=true
1012

11-
SMS_PROVIDER=console
12-
SMS_DEFAULT_FROM=Quest
13+
SMS_PROVIDER=mock
14+
SMS_DEFAULT_COUNTRY=US
15+
SMS_SENDER_ID=Quest
16+
SMS_STATUS_CALLBACK_URL=http://localhost:3007/receipts/webhook
17+
SMS_RATE_LIMIT_WINDOW_MINUTES=10
18+
SMS_RATE_LIMIT_MAX_PER_WINDOW=20
19+
SMS_OTP_RATE_LIMIT_WINDOW_MINUTES=10
20+
SMS_OTP_RATE_LIMIT_MAX_PER_WINDOW=3
21+
SMS_DISPATCH_INTERVAL_MS=5000
22+
SMS_DISPATCH_BATCH_SIZE=25
23+
SMS_RETRY_BASE_DELAY_MS=60000
24+
SMS_MAX_RETRIES=3
25+
SMS_OTP_LENGTH=6
26+
SMS_OTP_EXPIRY_MINUTES=10
27+
SMS_OTP_MAX_ATTEMPTS=5
28+
SMS_OTP_SECRET=replace-me
29+
SMS_OTP_TEMPLATE_NAME=otp-verification
30+
SMS_DEBUG_EXPOSE_CODES=false
1331

1432
TWILIO_ACCOUNT_SID=
1533
TWILIO_AUTH_TOKEN=
1634
TWILIO_FROM_NUMBER=
17-
TWILIO_STATUS_CALLBACK_URL=
35+
TWILIO_MESSAGING_SERVICE_SID=
1836

1937
AWS_REGION=us-east-1
20-
AWS_SNS_SENDER_ID=Quest
21-
22-
OTP_TTL_SECONDS=300
23-
OTP_LENGTH=6
24-
SMS_RATE_LIMIT_MAX=5
25-
SMS_RATE_LIMIT_WINDOW_SECONDS=60
38+
AWS_ACCESS_KEY_ID=
39+
AWS_SECRET_ACCESS_KEY=
40+
AWS_SNS_SENDER_ID=
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
dist
2+
node_modules
3+
.env
4+
coverage
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"singleQuote": true,
3+
"trailingComma": "all"
4+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Build stage
2+
FROM node:18-alpine AS builder
3+
4+
WORKDIR /app
5+
6+
COPY package*.json ./
7+
COPY tsconfig*.json ./
8+
COPY nest-cli.json ./
9+
10+
RUN npm ci
11+
12+
COPY src ./src
13+
14+
RUN npm run build
15+
16+
FROM node:18-alpine
17+
18+
WORKDIR /app
19+
20+
RUN apk add --no-cache dumb-init
21+
22+
COPY package*.json ./
23+
RUN npm ci --only=production && npm cache clean --force
24+
25+
COPY --from=builder /app/dist ./dist
26+
27+
RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001
28+
29+
USER nodejs
30+
31+
EXPOSE 3007
32+
33+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
34+
CMD wget --no-verbose --tries=1 --spider http://localhost:3007/health || exit 1
35+
36+
ENTRYPOINT ["dumb-init", "--"]
37+
CMD ["node", "dist/main.js"]
Lines changed: 104 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,123 @@
11
# SMS Service
22

3-
NestJS microservice for sending verification codes, alerts, and time-sensitive text messages.
3+
A NestJS microservice for sending SMS verification codes, alerts, reminders, and other time-sensitive notifications.
44

55
## Features
66

7-
- SMS, message, and delivery receipt entities backed by TypeORM/PostgreSQL
8-
- Provider abstraction with local console sending and Twilio HTTP support
9-
- Secure OTP generation with hashed verification codes and expiry
10-
- Handlebars message templates
11-
- Scheduled message dispatch through `@nestjs/schedule`
12-
- Delivery receipt webhook endpoint
13-
- Phone validation, per-phone rate limiting, history, and analytics
14-
- Docker and docker-compose configuration
7+
- Twilio, AWS SNS, and built-in mock provider support
8+
- Secure OTP generation and verification
9+
- Message templates with Handlebars rendering
10+
- Scheduled delivery with automatic retry backoff
11+
- Delivery receipts and webhook/manual confirmation endpoints
12+
- Phone number normalization and validation
13+
- Message history and analytics
14+
- Rate limiting per phone number and stricter OTP throttling
1515

16-
## Local Run
16+
## Quick Start
1717

1818
```bash
1919
cd microservices/sms-service
2020
npm install
21+
cp .env.example .env
2122
npm run start:dev
2223
```
2324

24-
The default `SMS_PROVIDER=console` logs outbound SMS messages and marks them sent. Set `SMS_PROVIDER=twilio` with `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`, and `TWILIO_FROM_NUMBER` to send real SMS.
25+
The default configuration uses the `mock` provider, so the service can run locally without Twilio or AWS credentials.
2526

26-
## API
27+
## Key Environment Variables
2728

28-
- `GET /health`
29-
- `POST /sms/send`
30-
- `POST /sms/send-template`
31-
- `POST /sms/otp`
32-
- `POST /sms/otp/verify`
33-
- `POST /sms/receipts`
34-
- `GET /sms`
35-
- `GET /sms/:id`
36-
- `POST /sms/:id/cancel`
37-
- `GET /sms-analytics`
38-
- `GET /sms-templates`
29+
| Variable | Description | Default |
30+
|---|---|---|
31+
| `SERVICE_PORT` | HTTP port | `3007` |
32+
| `DB_TYPE` | `postgres` or `sqljs` | `postgres` |
33+
| `SMS_PROVIDER` | `mock`, `twilio`, or `sns` | `mock` |
34+
| `SMS_DEFAULT_COUNTRY` | Phone parsing fallback region | `US` |
35+
| `SMS_DISPATCH_INTERVAL_MS` | Poll interval for scheduled/retry messages | `5000` |
36+
| `SMS_RATE_LIMIT_MAX_PER_WINDOW` | Max SMS per phone per window | `20` |
37+
| `SMS_OTP_RATE_LIMIT_MAX_PER_WINDOW` | Max OTP sends per phone per window | `3` |
38+
| `SMS_OTP_EXPIRY_MINUTES` | OTP expiration | `10` |
39+
| `SMS_DEBUG_EXPOSE_CODES` | Include OTP codes in responses for local testing | `false` |
3940

40-
## Docker
41+
## Main Endpoints
42+
43+
### Service
44+
45+
- `GET /` - service info
46+
- `GET /health` - health check
47+
48+
### SMS
49+
50+
- `POST /sms/send` - send plain SMS
51+
- `POST /sms/send-templated` - send SMS from template
52+
- `GET /sms/history` - list message history
53+
- `GET /sms/stats` - aggregated delivery analytics
54+
- `POST /sms/:id/cancel` - cancel pending/scheduled SMS
55+
- `POST /sms/:id/retry` - retry failed SMS
56+
57+
### Templates
58+
59+
- `POST /templates` - create template
60+
- `GET /templates` - list templates
61+
- `GET /templates/:id` - fetch template
62+
- `GET /templates/name/:name` - fetch template by name
63+
- `PUT /templates/:id` - update template
64+
- `DELETE /templates/:id` - delete template
65+
- `POST /templates/render` - render a template payload
66+
67+
### OTP
68+
69+
- `POST /otp/send` - generate and send verification code
70+
- `POST /otp/verify` - verify a code
71+
72+
### Receipts
73+
74+
- `POST /receipts/confirm` - manually confirm/update delivery status
75+
- `POST /receipts/webhook` - provider webhook target
76+
- `GET /receipts/message/:messageId` - list receipt events for a message
77+
78+
## Example Requests
79+
80+
### Send an Alert
4181

4282
```bash
43-
cd microservices/sms-service
44-
docker compose -f docker/docker-compose.yml up --build
83+
curl -X POST http://localhost:3007/sms/send \
84+
-H "Content-Type: application/json" \
85+
-d '{
86+
"phoneNumber": "+15555550123",
87+
"body": "Your Quest account password was changed.",
88+
"type": "alert",
89+
"priority": "high"
90+
}'
4591
```
92+
93+
### Create a Template
94+
95+
```bash
96+
curl -X POST http://localhost:3007/templates \
97+
-H "Content-Type: application/json" \
98+
-d '{
99+
"name": "otp-verification",
100+
"body": "Your {{purpose}} code is {{code}}. It expires in {{expiryMinutes}} minutes.",
101+
"category": "otp"
102+
}'
103+
```
104+
105+
### Send OTP
106+
107+
```bash
108+
curl -X POST http://localhost:3007/otp/send \
109+
-H "Content-Type: application/json" \
110+
-d '{
111+
"phoneNumber": "+15555550123",
112+
"purpose": "login"
113+
}'
114+
```
115+
116+
## Testing
117+
118+
```bash
119+
npm run build
120+
npm run test:e2e
121+
```
122+
123+
The e2e test suite runs against `sqljs` and the mock provider, so it does not require PostgreSQL or a real SMS account.

microservices/sms-service/docker/docker-compose.yml

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,32 @@ services:
55
container_name: sms-service
66
build:
77
context: ..
8-
dockerfile: docker/Dockerfile
8+
dockerfile: Dockerfile
99
ports:
10-
- '3010:3010'
10+
- '3007:3007'
1111
environment:
1212
- NODE_ENV=development
13-
- SERVICE_PORT=3010
13+
- SERVICE_PORT=3007
14+
- DB_TYPE=postgres
1415
- DB_HOST=postgres
1516
- DB_PORT=5432
1617
- DB_USER=${DB_USER:-postgres}
1718
- DB_PASSWORD=${DB_PASSWORD:-postgres}
1819
- DB_NAME=${DB_NAME:-sms_service}
19-
- SMS_PROVIDER=${SMS_PROVIDER:-console}
20-
- SMS_DEFAULT_FROM=${SMS_DEFAULT_FROM:-Quest}
20+
- DB_SYNC=true
21+
- SMS_PROVIDER=${SMS_PROVIDER:-mock}
22+
- SMS_DEFAULT_COUNTRY=${SMS_DEFAULT_COUNTRY:-US}
23+
- SMS_SENDER_ID=${SMS_SENDER_ID:-Quest}
24+
- SMS_STATUS_CALLBACK_URL=${SMS_STATUS_CALLBACK_URL:-http://localhost:3007/receipts/webhook}
25+
- SMS_OTP_SECRET=${SMS_OTP_SECRET:-replace-me}
2126
- TWILIO_ACCOUNT_SID=${TWILIO_ACCOUNT_SID:-}
2227
- TWILIO_AUTH_TOKEN=${TWILIO_AUTH_TOKEN:-}
2328
- TWILIO_FROM_NUMBER=${TWILIO_FROM_NUMBER:-}
24-
- TWILIO_STATUS_CALLBACK_URL=${TWILIO_STATUS_CALLBACK_URL:-}
25-
- OTP_TTL_SECONDS=${OTP_TTL_SECONDS:-300}
26-
- OTP_LENGTH=${OTP_LENGTH:-6}
27-
- SMS_RATE_LIMIT_MAX=${SMS_RATE_LIMIT_MAX:-5}
28-
- SMS_RATE_LIMIT_WINDOW_SECONDS=${SMS_RATE_LIMIT_WINDOW_SECONDS:-60}
29+
- TWILIO_MESSAGING_SERVICE_SID=${TWILIO_MESSAGING_SERVICE_SID:-}
30+
- AWS_REGION=${AWS_REGION:-us-east-1}
31+
- AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-}
32+
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-}
33+
- AWS_SNS_SENDER_ID=${AWS_SNS_SENDER_ID:-Quest}
2934
depends_on:
3035
postgres:
3136
condition: service_healthy
@@ -39,19 +44,18 @@ services:
3944
environment:
4045
- POSTGRES_USER=${DB_USER:-postgres}
4146
- POSTGRES_PASSWORD=${DB_PASSWORD:-postgres}
42-
- POSTGRES_DB=sms_service
47+
- POSTGRES_DB=${DB_NAME:-sms_service}
4348
ports:
44-
- '5440:5432'
49+
- '5437:5432'
4550
volumes:
4651
- postgres_sms_data:/var/lib/postgresql/data
47-
- ./init-db.sql:/docker-entrypoint-initdb.d/init-db.sql
48-
networks:
49-
- sms-network
5052
healthcheck:
5153
test: ["CMD-SHELL", "pg_isready -U postgres"]
5254
interval: 10s
5355
timeout: 5s
5456
retries: 5
57+
networks:
58+
- sms-network
5559
restart: unless-stopped
5660

5761
volumes:

0 commit comments

Comments
 (0)