code examples
code examples
Send SMS with Node.js, Express, and Vonage: A Developer Guide
A step-by-step guide to building a Node.js and Express application for sending SMS messages using the Vonage API, covering setup, implementation, testing, and security.
This guide provides a step-by-step walkthrough for building a simple Node.js application using the Express framework to send SMS messages via the Vonage SMS API. We will cover everything from project setup and configuration to implementation, testing, and basic security considerations.
By the end of this guide, you will have a functional Express API endpoint capable of accepting a phone number and message content, and then using the Vonage API to deliver that message as an SMS.
Technologies Used:
- Node.js: A JavaScript runtime environment for server-side development.
- Express: A minimal and flexible Node.js web application framework.
- Vonage SMS API: A service enabling programmatic sending and receiving of SMS messages globally. We'll use the
@vonage/server-sdkfor Node.js. - dotenv: A module to load environment variables from a
.envfile intoprocess.env.
Prerequisites:
- Node.js and npm (or yarn) installed on your machine.
- A Vonage API account (Sign up via the Vonage Developer Portal - new accounts usually receive free credits).
- A text editor or IDE (like VS Code).
- Basic understanding of Node.js, JavaScript, and REST APIs.
- A tool for making HTTP requests (like
curl, Postman, or Insomnia).
System Architecture:
The application follows a simple architecture:
- Client (e.g.,
curl, Postman, Frontend App): Sends a POST request to our Express API endpoint (/send-sms) with the recipient's phone number and the message text. - Express API (Our Node.js App): Receives the request, validates the input (basic validation in this guide), uses the Vonage Node.js SDK to interact with the Vonage API, passing the necessary credentials and message details.
- Vonage API: Receives the request from our application, processes it, and sends the SMS message to the specified recipient phone number.
- Recipient: Receives the SMS message on their mobile device.
[Client] ---- HTTP POST ----> [Express API (Node.js)] ---- Vonage SDK ----> [Vonage API] ---- SMS ----> [Recipient]
<---- HTTP Response <--Note: This guide focuses on sending SMS. Handling delivery status updates, which involves Vonage sending data back to your API via webhooks, is not covered here.
1. Setting up the Project
Let's start by creating our project directory and initializing our Node.js application.
-
Create Project Directory: Open your terminal or command prompt and create a new directory for the project, then navigate into it.
bashmkdir vonage-sms-guide cd vonage-sms-guide -
Initialize Node.js Project: Initialize the project using npm. The
-yflag accepts the default settings.bashnpm init -yThis creates a
package.jsonfile. -
Install Dependencies: We need
expressfor the web server,@vonage/server-sdkto interact with the Vonage API, anddotenvto manage our API credentials securely.bashnpm install express @vonage/server-sdk dotenv -
Configure
package.jsonfor ES Modules: Open thepackage.jsonfile and add the following line to enable the use of modernimport/exportsyntax:json{ ""name"": ""vonage-sms-guide"", ""version"": ""1.0.0"", ""description"": """", ""main"": ""index.js"", ""scripts"": { ""start"": ""node index.js"", ""test"": ""echo \""Error: no test specified\"" && exit 1"" }, ""keywords"": [], ""author"": """", ""license"": ""ISC"", ""dependencies"": { ""@vonage/server-sdk"": ""^3.0.0"", ""dotenv"": ""^16.0.0"", ""express"": ""^4.18.0"" }, ""type"": ""module"" }Why ES Modules? Using
""type"": ""module""allows us to use theimportandexportsyntax standard in modern JavaScript, which is cleaner and preferred over the older CommonJSrequire()syntax. Note: The version numbers (^3.x.x, etc.) shown are examples. You should typically install the latest stable versions unless you have specific compatibility requirements. -
Create Core Files: Create the main application file and a helper file for the Vonage logic.
bashtouch index.js lib.js .env .gitignore -
Configure
.gitignore: Prevent sensitive files and unnecessary directories from being committed to version control. Open.gitignoreand add:textnode_modules .envWhy ignore
.env? The.envfile will contain your secret API credentials. It should never be committed to Git or any public repository to prevent security breaches.
2. Vonage Account and API Credentials
To use the Vonage API, you need an account and API credentials.
-
Sign Up/Log In: Go to the Vonage Dashboard and sign up or log in.
-
Find API Credentials:
- Navigate to the ""API settings"" section in your Vonage Dashboard (often accessible directly from the main dashboard page or under your account name/settings).
- You will find your API key and API secret. Keep these secure.
- Dashboard Navigation: The exact location might change, but typically look for sections labeled ""API Keys,"" ""Credentials,"" or ""Settings.""
-
Configure Environment Variables: Open the
.envfile you created earlier and add your Vonage API credentials and a sender ID.dotenv# Vonage API Credentials VONAGE_API_KEY=YOUR_API_KEY VONAGE_API_SECRET=YOUR_API_SECRET # Sender ID (Use a purchased Vonage number, or an Alphanumeric Sender ID like 'MyAppSMS') # Note: Alphanumeric Sender IDs may have restrictions in some countries and might not support replies. # For testing with trial accounts, you might need to use 'Vonage APIs' as the sender or leave it blank if using test numbers only. VONAGE_SENDER_ID=MyAppSMS # Server Port PORT=3000- Replace
YOUR_API_KEYandYOUR_API_SECRETwith the actual values from your dashboard. VONAGE_SENDER_ID: This is the 'From' number or name displayed on the recipient's phone. You can use a virtual number purchased from Vonage (in E.164 format, e.g.,+12015550123) or an Alphanumeric Sender ID (up to 11 characters, e.g.,MyAppSMS). Some countries have restrictions on Alphanumeric Sender IDs. If using a trial account without a purchased number, you might need to omit this or use the default provided during signup.PORT: Defines the port your Express server will run on.
- Replace
-
Configure Test Numbers (Trial Accounts):
- Crucial for Trial Accounts: If you are using a free trial or haven't added billing information, Vonage requires you to pre-register and verify the phone numbers you want to send SMS to. Sending to non-registered numbers will fail.
- Dashboard Navigation: Go to your Vonage Dashboard -> ""Numbers"" -> ""Test numbers"".
- Add the recipient phone number(s) you will use for testing. Vonage will send a verification code via SMS or voice call to confirm ownership.
- Error Indication: If you try to send an SMS to a non-whitelisted number on a trial account, you will likely receive a
Non-Whitelisted Destinationerror (Status code 6).
3. Implementing the SMS Sending Logic
Now, let's write the code to handle incoming requests and send SMS messages.
-
Create SMS Sending Helper (
lib.js): This file will encapsulate the logic for interacting with the Vonage SDK.javascript// lib.js import { Vonage } from '@vonage/server-sdk'; import 'dotenv/config'; // Load .env variables into process.env // Initialize Vonage client const vonage = new Vonage({ apiKey: process.env.VONAGE_API_KEY, apiSecret: process.env.VONAGE_API_SECRET }); // Retrieve sender ID from environment variables const sender = process.env.VONAGE_SENDER_ID; /** * Sends an SMS message using the Vonage API. * @param {string} recipient - The recipient's phone number in E.164 format (e.g., +12015550123). * @param {string} message - The text message content. * @returns {Promise<object>} A promise that resolves with the Vonage API response data on success. * @throws {Error} Throws an error if sending fails, the API returns an error status, or the SDK encounters an issue. */ export const sendSms = async (recipient, message) => { console.log(`Attempting to send SMS from ${sender} to ${recipient}`); try { // Use the promise-based send method from the SDK const resp = await vonage.sms.send({ to: recipient, from: sender, text: message }); // Check the status of the first message in the response // Vonage API returns status '0' for success if (resp.messages[0].status === '0') { console.log('Message sent successfully:', resp.messages[0]); return resp; // Return the successful response data } else { // Log and throw an error if the status indicates failure const errorText = resp.messages[0]['error-text']; console.error(`Message failed with error: ${errorText}`); throw new Error(`Message failed: ${errorText} (Status: ${resp.messages[0].status})`); } } catch (err) { // Catch errors from the SDK call itself OR the error thrown above from API status check console.error('Error sending SMS via Vonage SDK or API:', err); // Re-throw the error to be caught by the caller (in index.js) // Ensure it's an Error object for consistent handling if (err instanceof Error) { throw err; } else { // If the caught object isn't an Error (less common with modern SDKs), wrap it throw new Error(`Vonage SDK Error: ${JSON.stringify(err)}`); } } };Code Explanation:
- We import the
Vonageclass anddotenv/config(which ensures environment variables are loaded immediately). - We instantiate
Vonageusing the API key and secret loaded from.env. - The
sendSmsfunction isasyncand takes therecipientnumber andmessagetext. - It uses
await vonage.sms.send()provided by the SDK v3+. - It checks
resp.messages[0].status. A status of'0'indicates success. Any other status indicates an error, and anErrorobject is thrown. - We log relevant information for debugging.
- The function returns the success response or throws an
Errorobject containing details from the API or the SDK, which will be caught by the caller.
- We import the
-
Create Express Server (
index.js): This file sets up the Express server and defines the API endpoint.javascript// index.js import express from 'express'; import 'dotenv/config'; // Load .env variables import { sendSms } from './lib.js'; // Import our SMS sending function // Initialize Express app const app = express(); // Middleware to parse JSON and URL-encoded request bodies app.use(express.json()); app.use(express.urlencoded({ extended: true })); // Define the port from environment variable or default to 3000 const PORT = process.env.PORT || 3000; // Simple root route for health check/info app.get('/', (req, res) => { res.send(`Vonage SMS Sender API running on port ${PORT}. Use POST /send-sms to send a message.`); }); // API endpoint to send SMS app.post('/send-sms', async (req, res) => { const { recipient, message } = req.body; // Basic Input Validation if (!recipient || !message) { console.error('Validation Error: Missing recipient or message in request body'); return res.status(400).json({ success: false, error: 'Missing required fields: recipient, message' }); } // Basic recipient format check (starts with '+'). IMPORTANT: This is NOT sufficient for production. // For production, use a robust library like 'libphonenumber-js' to validate E.164 format. if (!recipient.startsWith('+')) { console.error('Validation Error: Recipient number must be in E.164 format (e.g., +12015550123)'); return res.status(400).json({ success: false, error: 'Invalid recipient format. Use E.164 format (e.g., +12015550123).' }); } try { console.log(`Received request to send SMS to ${recipient}`); const result = await sendSms(recipient, message); // Call the async sendSms function // Send success response back to the client res.status(200).json({ success: true, data: result // Include the Vonage response data }); } catch (error) { // Log the detailed error on the server (error object comes from sendSms) console.error('API Error: Failed to send SMS:', error.message || error); // Send generic error response back to the client res.status(500).json({ success: false, error: 'Failed to send SMS.', // Optionally include non-sensitive error details in development // details: process.env.NODE_ENV === 'development' ? error.message : undefined }); } }); // Start the server app.listen(PORT, () => { console.log(`Server listening at http://localhost:${PORT}`); });Code Explanation:
- We import
express,dotenv/config, and oursendSmsfunction. - We initialize the Express application and apply middleware (
express.json,express.urlencoded) to parse incoming request bodies. - We define a
PORT. - A simple
GET /route is added for basic confirmation that the server is running. - The core logic resides in the
POST /send-smsendpoint, which is markedasync. - It extracts
recipientandmessagefrom the request body (req.body). - Basic validation is performed. Note: Production apps need much more robust validation, ideally using a library like
libphonenumber-jsas mentioned in the Security section. - It calls
sendSmsusingawaitwithin atry...catchblock to handle asynchronous operations and potential errors gracefully. - On success, it sends a
200 OKresponse with{ success: true, data: ... }. - On failure (caught in the
catchblock), it logs the error server-side (using the error object thrown bysendSms) and sends a500 Internal Server Errorresponse with{ success: false, error: ... }. - Finally,
app.listenstarts the server.
- We import
4. Running and Testing the Application
Now, let's run the server and test the endpoint.
-
Start the Server: In your terminal, run:
bashnpm startYou should see the output:
Server listening at http://localhost:3000 -
Test with
curl: Open a new terminal window. Replace+1xxxxxxxxxxwith a verified test number from your Vonage dashboard (including the+and country code) and customize the message.bashcurl -X POST http://localhost:3000/send-sms \ -H ""Content-Type: application/json"" \ -d '{ ""recipient"": ""+1xxxxxxxxxx"", ""message"": ""Hello from Node.js and Vonage!"" }' -
Test with Postman/Insomnia:
- Create a new request.
- Set the method to
POST. - Set the URL to
http://localhost:3000/send-sms. - Go to the ""Body"" tab, select ""raw"", and choose ""JSON"" from the dropdown.
- Enter the request body:
json
{ ""recipient"": ""+1xxxxxxxxxx"", ""message"": ""Hello from Node.js and Vonage!"" } - Send the request.
-
Expected Success Response: If the request is successful and the SMS is accepted by Vonage, you should receive a
200 OKresponse similar to this:json{ ""success"": true, ""data"": { ""messages"": [ { ""to"": ""1xxxxxxxxxx"", ""message-id"": ""xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"", ""status"": ""0"", ""remaining-balance"": ""1.85340000"", ""message-price"": ""0.00680000"", ""network"": ""12345"" } ], ""message-count"": ""1"" } }You should also receive the SMS on the specified recipient phone shortly after.
-
Example Error Response (Validation Failure): If you omit the
recipient:json{ ""success"": false, ""error"": ""Missing required fields: recipient, message"" }Status code:
400 Bad Request -
Example Error Response (Vonage API Failure): If sending fails due to an issue like an invalid API key or sending to a non-whitelisted number on a trial account:
json{ ""success"": false, ""error"": ""Failed to send SMS."" }Status code:
500 Internal Server Error(Check the server logs for more details likeMessage failed: Non Whitelisted Destination (Status: 6)).
5. Error Handling and Logging
While our basic example includes try...catch and logs errors, production applications require more robust strategies.
- Specific Error Handling: Catch specific Vonage error codes (like status
1for Throttled,2for Missing Params,5for Invalid Credentials,6for Non-Whitelisted Destination,9for Partner Quota Exceeded, etc.) and respond appropriately. You can parse theerror.messageor the status code from the error thrown bysendSms. - Logging Levels: Use a dedicated logging library like
winstonorpino. This enables different log levels (debug, info, warn, error), structured logging (JSON format for easier parsing by monitoring tools), and configurable outputs (console, files, external services).javascript// Conceptual example with Winston // npm install winston import winston from 'winston'; const logger = winston.createLogger({ level: 'info', format: winston.format.json(), transports: [ new winston.transports.Console({ format: winston.format.simple() }), // Add file or other transports for production ], }); // Replace console.log/error with logger.info/error // logger.error('API Error: Failed to send SMS:', error.message || error); - Retry Mechanisms: For transient network errors or rate limiting (status
1), implement a retry strategy, potentially with exponential backoff, using libraries likeasync-retry. Be cautious not to retry errors that are unlikely to succeed on retry (e.g., invalid credentials, invalid number). - Monitoring: Integrate with error tracking services (like Sentry, Datadog) to automatically capture and alert on exceptions in production.
6. Security Considerations
Even for this simple service, security is important.
- Secrets Management: Never hardcode API keys or secrets in your source code. Use environment variables (
.envfor local development, secure configuration management in deployment environments). Ensure.envis in your.gitignore. - Input Validation: Sanitize and validate all inputs from the client (
recipient,message).- Recipient: Use a robust library (e.g.,
libphonenumber-js) to validate E.164 format accurately. Our currentstartsWith('+')check is insufficient for production. - Message: Check for maximum length (standard SMS is 160 GSM-7 characters, longer messages are concatenated), potentially sanitize against harmful input (like script tags, though SMS is generally text-only), and enforce business rules. Libraries like
joiorexpress-validatorcan structure this validation.
- Recipient: Use a robust library (e.g.,
- Rate Limiting: Protect your endpoint from abuse and accidental loops by implementing rate limiting. The
express-rate-limitmiddleware is easy to integrate.javascript// Example using express-rate-limit // npm install express-rate-limit import rateLimit from 'express-rate-limit'; const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // Limit each IP to 100 requests per windowMs standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers legacyHeaders: false, // Disable the `X-RateLimit-*` headers message: 'Too many requests from this IP, please try again after 15 minutes' }); // Apply the rate limiting middleware to API routes app.use('/send-sms', limiter); // Apply specifically to the SMS endpoint - Authentication/Authorization: This example API is open. In a real application, you would protect this endpoint. Methods include:
- API Keys: Require clients to send a unique API key in a header (
Authorization: ApiKey YOUR_CLIENT_KEY). Validate the key server-side. - JWT Tokens: For user-specific actions, use JSON Web Tokens.
- IP Whitelisting: Restrict access to specific IP addresses if applicable.
- API Keys: Require clients to send a unique API key in a header (
7. Troubleshooting and Caveats
Non-Whitelisted Destination(Status 6): The most common issue for trial accounts. Ensure the recipient number is added and verified under ""Numbers"" > ""Test numbers"" in the Vonage Dashboard.Invalid Credentials(Status 5): Double-check yourVONAGE_API_KEYandVONAGE_API_SECRETin your.envfile. Ensure the file is being loaded correctly (theimport 'dotenv/config'should be early in your files).Invalid Sender Address (From)(Status 15): Ensure yourVONAGE_SENDER_IDis either a valid purchased Vonage number (in E.164 format) or a correctly formatted Alphanumeric Sender ID allowed in the destination country. If using a trial account without a purchased number, try removing theVONAGE_SENDER_IDor using the default assigned by Vonage.- Incorrect Recipient Format: Ensure the
recipientnumber starts with+and includes the country code (E.164 format). Use a validation library for robustness. - Insufficient Funds: Check your Vonage account balance.
- SDK Version Issues: Ensure you are using compatible versions of Node.js and the
@vonage/server-sdk. Check the SDK's documentation if you encounter unexpected behavior.
8. Deployment (Conceptual)
Deploying this application involves running it on a server accessible via the internet.
- Platforms: Services like Heroku, Vercel, Render, AWS (EC2, Lambda, Elastic Beanstalk), Google Cloud (Cloud Run, App Engine), or DigitalOcean (App Platform, Droplets) can host Node.js applications.
- Environment Variables: Crucially, configure your
VONAGE_API_KEY,VONAGE_API_SECRET, andVONAGE_SENDER_IDsecurely within your chosen deployment platform's environment variable settings. Do not commit your.envfile. - Port: Ensure your application listens on the port specified by the environment (often via
process.env.PORT), as done inindex.js. npm install: The deployment process must runnpm install(ornpm cifor cleaner installs) to fetch dependencies.- Start Command: The platform needs to know how to start your app (e.g.,
npm start). - CI/CD: For robust deployments, set up a Continuous Integration/Continuous Deployment pipeline (using GitHub Actions, GitLab CI, Jenkins, etc.) to automate testing and deployment.
9. Verification and Testing
Beyond the manual curl/Postman tests:
- Manual Verification Checklist:
-
- Server starts without errors (
npm start).
- Server starts without errors (
-
-
GET /returns the expected info message.
-
-
-
POST /send-smswith valid data returns200 OKandsuccess: true.
-
-
- The SMS is received on the (verified test) recipient phone.
-
-
POST /send-smswith missing fields returns400 Bad Requestandsuccess: false.
-
-
-
POST /send-smswith invalid recipient format returns400 Bad Request.
-
-
- (If possible to test)
POST /send-smswith invalid credentials results in a500 Internal Server Erroron the client and specific error logs on the server.
- (If possible to test)
-
- Check Vonage Dashboard logs (""Logs"" -> ""SMS"") for message status details.
-
- Automated Testing:
- Unit Tests: Use a framework like
JestorMocha/Chaito test thesendSmsfunction in isolation. Mock the@vonage/server-sdkto simulate success and failure responses without actually calling the API. - Integration Tests: Use a library like
supertestto make HTTP requests to your running Express application (or an in-memory instance) and assert the responses for different scenarios (/send-smssuccess, validation errors, etc.). These tests could potentially hit the actual Vonage API in a controlled testing environment if needed, but mocking is usually preferred.
- Unit Tests: Use a framework like
This guide provides a solid foundation for sending SMS messages using Node.js, Express, and Vonage. Remember to enhance error handling, add robust input validation (especially for phone numbers using libraries like libphonenumber-js), implement proper security measures like authentication and rate limiting, and set up comprehensive logging and monitoring for production environments. Refer to the official Vonage Server SDK for Node.js documentation and the Vonage SMS API reference for more advanced features and details.
Frequently Asked Questions
How to send SMS with Node.js and Express?
This guide details building a Node.js application with Express to send SMS messages using the Vonage SMS API. You'll set up a project, configure API credentials, implement sending logic, and test the application. The Vonage Node.js SDK (`@vonage/server-sdk`) is used for interacting with the API.
What is the Vonage SMS API?
The Vonage SMS API is a service that allows developers to send and receive SMS messages programmatically across the globe. This guide uses the `@vonage/server-sdk` for Node.js to interact with the Vonage API for sending SMS messages within your Node.js/Express app.
Why use dotenv in Node.js for SMS?
Dotenv is used to load environment variables from a `.env` file. This helps in managing sensitive API credentials securely, preventing them from being exposed directly in the source code, improving security, and making it easier to manage different configurations across environments.
When should I verify test numbers with Vonage?
Verification of test recipient phone numbers is required when using a Vonage free trial account. Add your test numbers in the Vonage Dashboard under "Numbers" -> "Test numbers." This is essential because trial accounts can only send SMS to these verified numbers. Failure to do so will result in a "Non-Whitelisted Destination" error.
Can I use alphanumeric sender ID with Vonage?
Yes, you can use an alphanumeric sender ID (up to 11 characters), such as 'MyAppSMS', with Vonage. However, be aware of potential restrictions. Some countries may not allow alphanumeric sender IDs, and replies might not be supported. With trial accounts, using the default sender ID provided by Vonage might be needed for testing purposes.
How to set up Vonage API credentials for Node.js?
Obtain your API Key and API Secret from your Vonage Dashboard. Create a `.env` file in your project root. Inside this file, add `VONAGE_API_KEY=your_api_key`, `VONAGE_API_SECRET=your_api_secret`, and `VONAGE_SENDER_ID=your_sender_id`, replacing placeholders with your actual credentials.
What is the role of express.json() middleware?
The `express.json()` middleware is used to parse incoming requests with JSON payloads. It takes the raw request body and converts it into a JavaScript object. This processed data is then attached to the `req.body` property, which is used by the `/send-sms` route handler to extract the `recipient` and `message`.
How to test Vonage SMS API integration locally?
You can test your local setup using tools like `curl`, Postman, or Insomnia. Send `POST` requests to `http://localhost:3000/send-sms` with JSON payloads containing the `recipient` and `message`. The server should return success and the SMS should arrive on the phone number shortly after.
Why is input validation important for SMS sending?
Input validation protects against security vulnerabilities and ensures only correctly formatted data is processed. For phone numbers, use a library like `libphonenumber-js` to validate E.164 format. For message content, check maximum length, sanitize against potentially harmful input, and enforce business-specific rules.
How to implement rate limiting in Node.js/Express?
The `express-rate-limit` middleware is recommended to prevent abuse and accidental request overload. Install via `npm install express-rate-limit` and configure it to limit the number of requests per IP address within a specific timeframe. The guide provides a specific example.
What are security best practices for a Vonage SMS app?
Key security measures include: never hardcoding API keys, using environment variables and robust secrets management in deployment; validating all user inputs; implementing rate limiting to prevent abuse; adding authentication or authorization layers (API keys, JWT, IP whitelisting) as appropriate to protect your API endpoint.
How to handle the 'Non-Whitelisted Destination' error?
This error (status 6) occurs with Vonage trial accounts when sending to unverified numbers. Ensure the recipient is added in 'Numbers' > 'Test numbers' on the Vonage Dashboard. Each number must be registered and verified to be used with the API during the free trial.
What does 'Invalid Sender Address' error mean?
The 'Invalid Sender Address' (status 15) error happens if the `VONAGE_SENDER_ID` is incorrectly formatted or not provisioned in your Vonage account. Verify that it's a purchased number (E.164) or an allowed Alphanumeric Sender ID for your destination. Try removing or using the Vonage-assigned default during the trial phase.
How to troubleshoot Vonage SMS API integration issues?
Common issues include 'Non-Whitelisted Destination', 'Invalid Credentials', or 'Invalid Sender Address.' Check server logs for detailed error messages and Vonage dashboard logs. Consult the Vonage Server SDK and API documentation. Review error handling section.
How to deploy a Node.js Vonage SMS application?
Deployment involves hosting your app on platforms like Heroku, AWS, or Google Cloud. Use platform-specific mechanisms for setting environment variables securely, configuring ports, running npm install, and defining the start command. Use CI/CD for automated deployment and testing.