A serverless microservice for sending SMS and email notifications via AWS Lambda.
π New to this service? Check out the Quick Start Guide to get started in minutes!
π Looking for detailed documentation? See the Usage Guide for comprehensive examples and best practices.
FLC Notify Service is a notification microservice built for First Love Center projects. It provides a secure, RESTful API to send SMS messages and emails through a unified interface. The service is deployed as an AWS Lambda function with Express.js, providing reliable message delivery through integrated providers.
- π± SMS Notifications - Send SMS via MNotify with support for single or multiple recipients
- π§ Email Notifications - Send emails via Resend with HTML and plain text support
- π Push Notifications - Send push via Firebase Cloud Messaging (FCM HTTP v1) to device tokens or topics
- π Secure Authentication - API key-based authentication for all requests
- β‘ Serverless Deployment - Deployed as AWS Lambda function for auto-scaling
- π Automated CI/CD - GitHub Actions workflow for continuous deployment
- π Comprehensive Logging - Detailed logging for debugging and monitoring
- β Input Validation - Robust request validation with clear error messages
- π CORS Enabled - Cross-origin resource sharing support
This service is deployed as an AWS Lambda function behind AWS API Gateway and provides the following endpoints:
- GET
/- Health check endpoint - POST
/send-sms- Send SMS messages via MNotify - POST
/send-email- Send emails via Resend - POST
/send-push- Send push notifications via Firebase Cloud Messaging
- Runtime: Node.js 20.x
- Framework: Express.js with serverless-http
- Language: TypeScript
- SMS Provider: MNotify
- Email Provider: Resend
- Push Provider: Firebase Cloud Messaging (FCM HTTP v1, via
firebase-admin) - Secrets Management: AWS Secrets Manager
- Deployment: AWS Lambda + API Gateway
- CI/CD: GitHub Actions
- Node.js 20.x or higher
- npm 8.x or higher
- AWS CLI (configured with appropriate credentials)
- AWS Account with Lambda and Secrets Manager access
-
Clone the repository
git clone https://github.com/firstlovecenter/flc-notify-service.git cd flc-notify-service -
Install root dependencies
npm ci
-
Install function dependencies
cd functions/notify npm ci -
Set up AWS Secrets Manager
The service uses AWS Secrets Manager for secure credential storage. Create a secret with the following keys:
{ "FLC_NOTIFY_KEY": "your-api-authentication-key", "MNOTIFY_KEY": "your-mnotify-api-key", "RESEND_API_KEY": "your-resend-api-key", "TEST_PHONE_NUMBER": "optional-test-number", "FIREBASE_SERVICE_ACCOUNT": "{\"type\":\"service_account\",\"project_id\":\"flc-platform-dev\", ...}" }FIREBASE_SERVICE_ACCOUNTholds the entire Firebase service-account JSON as a single stringified value (the code readsSECRETS.FIREBASE_SERVICE_ACCOUNTandJSON.parses it). Use the dev project's key in the dev secret and the prod project's key in the prod secret. This value is a credential β never commit it, hardcode it, or log it. See Push Notification Setup for how to generate it. -
Configure environment variables
Create a
.envfile infunctions/notify/:AWS_SECRET_NAME=your-secret-name
-
Build the TypeScript code
npm run build
π For detailed usage instructions, code examples, and best practices, see USAGE_GUIDE.md
All API endpoints (except health check) require the x-secret-key header for authentication.
x-secret-key: YOUR_FLC_NOTIFY_KEY
Endpoint: GET /
Response:
{
"success": true,
"message": "Service is healthy"
}Endpoint: POST /send-sms
Headers:
Content-Type: application/json
x-secret-key: YOUR_SECRET_KEY
Request Body:
{
"recipient": "+233244000000",
"message": "Your message here",
"sender": "FLC Admin"
}Parameters:
recipient(required): Phone number or array of phone numbersmessage(required): SMS message contentsender(optional): Sender name (defaults to "FLC Admin")
Success Response (200 OK):
{
"success": true,
"message": "SMS sent successfully",
"data": {
"code": "2000"
}
}Error Responses:
400 Bad Request- Missing or invalid parameters403 Forbidden- Invalid or missing API key502 Bad Gateway- SMS provider error
Endpoint: POST /send-email
Headers:
Content-Type: application/json
x-secret-key: YOUR_SECRET_KEY
Request Body:
{
"from": "FL Accra Admin<no-reply@updates.firstlovecenter.com>",
"to": "recipient@example.com",
"subject": "Email Subject",
"html": "<p>Email content</p>",
"text": "Email content",
"replyTo": "support@firstlovecenter.com"
}Parameters:
from(required): Sender email addressto(required): Recipient email or array of emailssubject(required): Email subject linehtml(optional*): HTML email bodytext(optional*): Plain text email bodyreplyTo(optional): Reply-to email address
*Note: Must provide either html or text (or both)
Success Response (200 OK):
{
"success": true,
"message": "Email sent successfully",
"data": {
"id": "unique-email-id"
}
}Error Responses:
400 Bad Request- Missing or invalid parameters403 Forbidden- Invalid or missing API key502 Bad Gateway- Email provider error
Sends a push notification via Firebase Cloud Messaging (FCM HTTP v1). Phase 1 is a stateless relay: the caller passes explicit device tokens (and/or an FCM topic) plus the payload, and the service forwards to FCM and returns per-token results.
Endpoint: POST /send-push
Headers:
Content-Type: application/json
x-secret-key: YOUR_SECRET_KEY
Request Body:
{
"tokens": ["<fcm-device-token>", "..."],
"topic": null,
"notification": { "title": "Cycle summary", "body": "3 supervisors need review" },
"data": { "route": "/cycle-summary" }
}Parameters:
tokens(required*): Array of FCM device tokens (non-empty)topic(required*): FCM topic name to broadcast tonotification(required): Object withtitleand/orbodydata(optional): Flat key/value map of string data delivered with the message (FCM requires alldatavalues to be strings)
*Note: Provide exactly one of tokens or topic β not both, not neither.
Success Response β token multicast (200 OK):
A partial failure is not a total failure. The call returns 200 as long as FCM
processed the batch; inspect failureCount/failures for dead tokens to prune.
{
"success": true,
"message": "Push processed",
"data": {
"successCount": 2,
"failureCount": 1,
"failures": [
{
"index": 2,
"token": "<the-failed-token>",
"error": "messaging/registration-token-not-registered"
}
]
}
}Success Response β topic broadcast (200 OK):
{
"success": true,
"message": "Push sent successfully",
"data": { "messageId": "projects/flc-platform-dev/messages/1234567890", "successCount": 1, "failureCount": 0 }
}Error Responses:
400 Bad Request- Missing/invalid parameters (no target, both targets, or no notification content)403 Forbidden- Invalid or missing API key502 Bad Gateway- FCM / Firebase Admin error (e.g. bad service-account credential)
Phase 2 (not built): a centralized token registry (token β user β app) so callers could target a
userIdinstead of raw tokens. For now each app stores its own FCM tokens (e.g. Poimen stores them onMember) and passes them explicitly.
The push channel uses one Firebase project per environment, shared by all FLC apps (not one project per app):
| Environment | Firebase Project ID | Secrets Manager secret |
|---|---|---|
| dev | flc-platform-dev |
the dev notify secret |
| prod | flc-platform-prod |
the prod notify secret |
Both projects use the display name "First Love Center". Each app (Poimen, fl-admin-portal, Synago, β¦) is registered as a separate Android / iOS / web app within the environment's project.
These steps require Firebase Console / Google Cloud access and are not performed by
this codebase. Do them once per project (flc-platform-dev, then flc-platform-prod):
-
Create the Firebase project (
flc-platform-dev/flc-platform-prod) if it does not already exist, display name "First Love Center". -
Register each client app (Android/iOS/web) that will receive push.
-
iOS: upload the APNs auth key (
.p8) under Project settings β Cloud Messaging β Apple app configuration. -
Web: generate the VAPID key pair under Project settings β Cloud Messaging β Web configuration (clients use the public key; this service does not need it).
-
Generate a service-account key: Project settings β Service accounts β Generate new private key. This downloads a JSON file.
β οΈ If you see "Key creation is not allowed on this service account", your GCP organization enforces theiam.disableServiceAccountKeyCreationorg policy (a common secure default). To create the key: Google Cloud Console β IAM & Admin β Organization Policies β Disable service account key creation, scope to the project, Manage policy β Override parent's policy β Enforcement Off, save, wait ~1 min, then retry. Re-lock it afterward (set back to Inherit parent's policy) β re-enabling the block does not revoke keys already created. -
Store the service account in Secrets Manager: put the entire JSON, stringified, as
FIREBASE_SERVICE_ACCOUNTin that environment's notify secret (dev JSON β dev secret, prod JSON β prod secret). Do not commit or log this file. The code readsSECRETS.FIREBASE_SERVICE_ACCOUNTandJSON.parses it on cold start.
If you don't have console access for any of the above, treat it as a blocking prerequisite and hand it to whoever owns the FLC Firebase org β don't fake or commit a placeholder credential.
With a real dev device token and the dev secret populated:
curl -X POST https://your-dev-url/send-push \
-H "Content-Type: application/json" \
-H "x-secret-key: YOUR_DEV_FLC_NOTIFY_KEY" \
-d '{
"tokens": ["<real-dev-device-token>"],
"notification": { "title": "FLC test", "body": "Push works π" },
"data": { "route": "/cycle-summary" }
}'A successful delivery returns successCount: 1, failureCount: 0 and the notification
appears on the device. A dead/invalid token comes back in failures[] with an
error code (e.g. messaging/registration-token-not-registered) so the caller can
prune it β the rest of the batch still succeeds.
The service is automatically deployed to AWS Lambda when changes are pushed to the main branch that affect files in the functions/notify directory.
The CI/CD pipeline automatically:
- Triggers on pushes to
mainbranch affectingfunctions/notify/** - Installs dependencies
- Builds TypeScript code
- Packages the Lambda function with dependencies
- Deploys to AWS Lambda
- Sends deployment notification to Slack
If you need to deploy manually:
-
Build the TypeScript code:
cd functions/notify npm run build -
Package the Lambda function:
# From repository root mkdir -p lambda-package # Copy compiled JavaScript files cp functions/notify/*.js lambda-package/ # Copy lib folder mkdir -p lambda-package/lib cp lib/secrets.js lambda-package/lib/ # Copy package.json (production dependencies only) cp functions/notify/package.json lambda-package/ # Install production dependencies cd lambda-package npm ci --production --omit=dev
-
Create deployment ZIP:
# From lambda-package directory zip -r ../notify-lambda.zip . cd ..
-
Deploy to AWS Lambda:
aws lambda update-function-code \ --function-name flc-notify-service \ --zip-file fileb://notify-lambda.zip \ --region eu-west-2
After deployment, test the service:
# Health check
curl https://your-lambda-url.amazonaws.com/
# Test SMS (requires valid API key)
curl -X POST https://your-lambda-url.amazonaws.com/send-sms \
-H "Content-Type: application/json" \
-H "x-secret-key: YOUR_KEY" \
-d '{"recipient": "+233244000000", "message": "Test"}'This project uses GitHub Actions for continuous integration and deployment.
- Push to
mainbranch - Changes to files in
functions/notify/**directory
- Checkout - Checks out the repository code
- Setup Node.js - Configures Node.js 20.x environment
- Install Dependencies - Installs npm packages
- Build - Compiles TypeScript to JavaScript
- Package - Creates Lambda deployment package
- Deploy - Updates AWS Lambda function code
- Notify - Sends deployment status to Slack
The GitHub Actions workflow requires the following secrets:
AWS_ACCESS_KEY_ID- AWS credentials for deploymentAWS_SECRET_ACCESS_KEY- AWS credentials for deploymentAWS_REGION- AWS region (default: eu-west-2)SLACK_WEBHOOK_URL- For deployment notifications
flc-notify-service/
βββ .github/
β βββ workflows/ # GitHub Actions CI/CD
βββ functions/
β βββ notify/
β βββ index.ts # Main Lambda handler & routing
β βββ sendEmail.ts # Email sending logic (Resend)
β βββ sendSMS.ts # SMS sending logic (MNotify)
β βββ sendPush.ts # Push sending logic (Firebase Cloud Messaging)
β βββ secrets.ts # AWS Secrets Manager integration
β βββ utils.ts # Utility functions
β βββ package.json # Function dependencies
β βββ tsconfig.json # TypeScript configuration
βββ lib/
β βββ secrets.js # Compiled secrets module
βββ package.json # Root dependencies
βββ README.md # This file
βββ USAGE_GUIDE.md # Detailed usage documentation
- index.ts: Express.js application with routes, authentication middleware, and error handling
- sendSMS.ts: MNotify integration with validation and error handling
- sendEmail.ts: Resend integration with support for HTML/text emails
- sendPush.ts: Firebase Cloud Messaging integration; initializes
firebase-adminonce per warm Lambda and usessendEachForMulticastfor token batches - secrets.ts: AWS Secrets Manager client for secure credential management
- utils.ts: Helper functions for validation and common operations
-
Start development mode:
cd functions/notify npm run build:watch -
Run locally with serverless offline (optional):
# Install serverless framework npm install -g serverless # Run locally serverless offline start
-
Test endpoints:
# Health check curl http://localhost:3000/ # Send SMS curl -X POST http://localhost:3000/send-sms \ -H "Content-Type: application/json" \ -H "x-secret-key: YOUR_KEY" \ -d '{"recipient": "+233244000000", "message": "Test"}'
-
Create a feature branch:
git checkout -b feature/your-feature-name
-
Make changes and test locally
-
Build and verify no TypeScript errors:
npm run build
-
Commit and push changes:
git add . git commit -m "Description of changes" git push origin feature/your-feature-name
-
Create a Pull Request to
main -
After PR approval and merge, automatic deployment will trigger
Use the provided examples in USAGE_GUIDE.md to test the API endpoints.
-
Test Authentication:
# Should return 403 curl -X POST https://your-url/send-sms \ -H "Content-Type: application/json" \ -d '{"recipient": "+233244000000", "message": "Test"}'
-
Test Validation:
# Should return 400 - missing message curl -X POST https://your-url/send-sms \ -H "Content-Type: application/json" \ -H "x-secret-key: YOUR_KEY" \ -d '{"recipient": "+233244000000"}'
-
Test Success Case:
# Should return 200 curl -X POST https://your-url/send-sms \ -H "Content-Type: application/json" \ -H "x-secret-key: YOUR_KEY" \ -d '{"recipient": "+233244000000", "message": "Hello!"}'
Monitor Lambda function logs in AWS CloudWatch:
# View recent logs
aws logs tail /aws/lambda/flc-notify-service --follow-
"Unauthorized access" error
- Verify your API key in AWS Secrets Manager
- Check the
x-secret-keyheader is correctly set
-
"Missing [field] field" error
- Ensure all required fields are included in request body
- Check field names match exactly (case-sensitive)
-
SMS/Email not received
- Check CloudWatch logs for detailed error messages
- Verify phone number format includes country code (+233...)
- For emails, check spam folder
- Verify sender email domain is verified with Resend
-
Deployment failures
- Check GitHub Actions logs
- Verify AWS credentials are correctly configured
- Ensure Lambda function exists in AWS
For detailed troubleshooting, see the USAGE_GUIDE.md.
- API Keys: Never commit API keys to version control. Always use AWS Secrets Manager or environment variables.
- HTTPS Only: The service should only be accessed via HTTPS in production.
- Rate Limiting: Consider implementing rate limiting at the API Gateway level to prevent abuse.
- Input Validation: All inputs are validated before processing to prevent injection attacks.
- CORS: CORS is enabled but should be configured to allow only trusted domains in production.
We welcome contributions! Please follow these guidelines:
-
Fork the repository and create a feature branch
git checkout -b feature/amazing-feature
-
Make your changes following the existing code style
-
Test your changes thoroughly
-
Commit your changes with clear, descriptive messages
git commit -m "Add amazing feature" -
Push to your branch
git push origin feature/amazing-feature
-
Open a Pull Request to the
mainbranch
- Use TypeScript for all new code
- Follow existing naming conventions
- Add comments for complex logic
- Update documentation for API changes
ISC License
Copyright (c) 2026 First Love Center
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
- Repository: github.com/firstlovecenter/flc-notify-service
- Issues: Use GitHub Issues for bug reports and feature requests
- Author: John-Dag Addy
- MNotify for SMS delivery services
- Resend for email delivery services
- AWS Lambda for serverless hosting
Quick Links:
- οΏ½ Quick Start Guide - Get started in minutes
- π Detailed Usage Guide - Comprehensive documentation
- π API Documentation - API reference
- π Changelog - Version history
- π GitHub Repository