Integrate Sinch Batch SMS API with RedwoodJS for Marketing Campaigns
This guide provides a comprehensive, step-by-step tutorial for integrating the Sinch Batch SMS API into a RedwoodJS application to facilitate marketing campaigns. We will build a system enabling users to define campaign messages, manage contacts, and send SMS batches via Sinch directly from a RedwoodJS app.
This integration addresses the need for programmatic control over SMS marketing efforts, allowing for automated campaign creation, targeted sending based on application data, and centralized management within your existing RedwoodJS project. While this guide focuses on using the common Sinch Batch SMS API (/xms/v1/{SERVICE_PLAN_ID}/batches
) as a practical way to send campaign messages, please note that Sinch may offer dedicated ""Marketing Campaign"" APIs with additional features (like advanced list management or specific campaign tracking). You should consult the official Sinch documentation to determine the best API for your specific marketing requirements.
Technologies Used:
- RedwoodJS: A full-stack, serverless-first web application framework based on React, GraphQL, and Prisma. We leverage its structure for rapid development, built-in API layer, and CLI tools.
- Node.js: The runtime environment for the RedwoodJS API side, executing our backend logic and interacting with the Sinch API.
- Prisma: A next-generation ORM for Node.js and TypeScript, used by RedwoodJS for database modeling and access.
- Sinch Batch SMS API: The third-party service providing SMS sending functionality used here to implement marketing sends.
- GraphQL: Used by RedwoodJS for communication between the frontend (web side) and backend (api side).
System Architecture:
+-------------------+ +-----------------------+ +-----------------------+ +---------------------+
| RedwoodJS Web UI | ---> | RedwoodJS GraphQL API | ---> | Sinch API Client | ---> | Sinch Batch SMS API |
| (React Components)| | (api side) | | (api/src/lib/sinchClient) | | (REST) |
+-------------------+ +----------+------------+ +----------+------------+ +----------+----------+
| ^ | ^ |
| User Interaction | | GraphQL Query/Mutation | Sinch API Calls | SMS Sending
v | v | v
+-------------------+ +----------+------------+ +----------+------------+
| RedwoodJS Router | | RedwoodJS Services | | Prisma ORM |
| (Routes.tsx) | | (api/src/services) | | (schema.prisma) |
+-------------------+ +----------+------------+ +----------+------------+
| ^
| Database Operations |
v |
+----------+------------+
| Database (e.g., PostgreSQL) |
+-----------------------+
Final Outcome:
By the end of this guide, you will have:
- A RedwoodJS application configured to interact with the Sinch Batch SMS API.
- Database models for storing
Campaign
andContact
data. - RedwoodJS services for managing campaigns and contacts.
- A dedicated Node.js module for encapsulating Sinch API interactions.
- Basic UI components (Pages, Cells) for viewing and creating campaigns (implementation details omitted for brevity).
- Secure handling of Sinch API credentials.
- Basic error handling and logging for the integration.
- Instructions for deployment and verification.
Prerequisites:
- Node.js (v18 or later recommended) and Yarn v1 installed.
- Access to a terminal or command prompt.
- A Sinch account with SMS API access. You will need your Service Plan ID, an API Token, and a registered Sender ID.
- Basic understanding of RedwoodJS, React, GraphQL, Prisma, and REST APIs.
- A code editor (e.g., VS Code).
(Note: This guide assumes you are starting a new RedwoodJS project. Adapt steps accordingly if integrating into an existing one.)
1. Setting up the Project
This section covers creating a new RedwoodJS project and configuring the necessary environment.
-
Create RedwoodJS App: Open your terminal and run the RedwoodJS creation command. We'll name our project
redwood-sinch-campaigns
.yarn create redwood-app redwood-sinch-campaigns --typescript
--typescript
: Initializes the project with TypeScript for enhanced type safety.
-
Navigate to Project Directory:
cd redwood-sinch-campaigns
-
Environment Variables Setup: RedwoodJS uses
.env
files for environment variables. Create a.env
file in the project root for your Sinch credentials.- Create the file:
touch .env
- Add the following variables, replacing placeholders with your actual Sinch credentials:
# .env # Sinch API Credentials # Obtain from your Sinch Dashboard -> APIs -> Your SMS API -> API Credentials SINCH_SERVICE_PLAN_ID="YOUR_SERVICE_PLAN_ID" SINCH_API_TOKEN="YOUR_API_TOKEN" # Sinch Sender ID (Registered Number/Short Code/Alphanumeric) # This MUST be a sender ID approved for your account in the Sinch dashboard SINCH_SENDER_ID="YOUR_REGISTERED_SENDER_ID" # Sinch API Endpoint (Confirm the correct region endpoint if needed) SINCH_API_BASE_URL="https://us.sms.api.sinch.com" # Or eu.sms.api.sinch.com, etc. # Database URL (Example for local SQLite, replace for PostgreSQL/MySQL) DATABASE_URL="file:./dev.db"
-
Explanation:
SINCH_SERVICE_PLAN_ID
: Your unique identifier for the Sinch service plan.SINCH_API_TOKEN
: Your secret token for authenticating API requests. Treat this like a password.SINCH_SENDER_ID
: Your registered phone number, short code, or alphanumeric sender ID approved by Sinch for sending messages. This is required in thefrom
field of the SMS request.SINCH_API_BASE_URL
: The base URL for the Sinch REST API endpoints relevant to SMS. Adjust if your account is homed in a different region (e.g., EU).DATABASE_URL
: Connection string for your database (adjust provider inschema.prisma
accordingly).
-
Security: Ensure
.env
is added to your.gitignore
file (RedwoodJS usually adds it by default) to prevent accidentally committing secrets.
- Create the file:
-
Install Dependencies (Optional but Recommended): We'll use
node-fetch
for making HTTP requests to the Sinch API from our Node.js backend. While Node.js has a built-in fetch (experimental in older versions, stable in newer ones), explicitly addingnode-fetch
ensures compatibility and consistent behavior.yarn workspace api add node-fetch@^2 # Use v2 for CommonJS compatibility with Redwood api side yarn workspace api add -D @types/node-fetch@^2
yarn workspace api add ...
: Installs the package specifically for theapi
side of your RedwoodJS project.node-fetch@^2
: Version 2 is generally recommended for CommonJS environments like the default Redwood API side.@types/node-fetch@^2
: Adds TypeScript typings fornode-fetch
.
-
Project Structure Overview: RedwoodJS has a conventional structure:
api/
: Backend code (GraphQL API, services, database, Node.js logic).api/db/schema.prisma
: Database schema definition.api/src/functions/
: Serverless function handlers (GraphQL endpoint).api/src/graphql/
: GraphQL schema definitions (*.sdl.ts
).api/src/services/
: Business logic implementations.api/src/lib/
: Utility functions, third-party clients (like our Sinch client).
web/
: Frontend code (React components, pages, layouts).web/src/pages/
: Page components.web/src/components/
: Reusable UI components (including Cells).web/src/layouts/
: Layout components wrapping pages.web/src/Routes.tsx
: Frontend routing definitions.
.env
: Environment variables (ignored by Git).redwood.toml
: Project configuration.
2. Implementing Core Functionality (Database and Services)
We'll define our data models, apply migrations, and create RedwoodJS services to manage them.
-
Define Database Schema: Open
api/db/schema.prisma
and define models forContact
andCampaign
.// api/db/schema.prisma datasource db { provider = ""sqlite"" // Or ""postgresql"", ""mysql"" url = env(""DATABASE_URL"") } generator client { provider = ""prisma-client-js"" } model Contact { id Int @id @default(autoincrement()) phoneNumber String @unique // Assuming phone number is the unique identifier (E.164 format recommended) firstName String? lastName String? // Add status for opt-outs, etc. // status String @default(""ACTIVE"") // e.g., ACTIVE, OPTED_OUT createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Add other relevant fields like email, tags, etc. } model Campaign { id Int @id @default(autoincrement()) name String messageBody String // Basic status tracking status String @default(""DRAFT"") // e.g., DRAFT, SENDING, SENT, FAILED // Store the Batch ID returned by Sinch API after successful sending sinchBatchId String? @unique // Renamed for clarity based on API used scheduledAt DateTime? // Optional: For scheduling campaigns sentAt DateTime? // Record when sending was initiated/completed createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relation to contacts (can be complex - many-to-many, or simplified for now) // For simplicity here, we assume campaigns are sent to all contacts or a segment defined elsewhere. // A many-to-many relation might look like: // contacts Contact[] @relation(""CampaignContacts"") @@index([status]) // Add index for faster status queries } // If using many-to-many for contacts per campaign: // model _CampaignContacts { // A Int // B Int // @@id([A, B]) // campaign Campaign @relation(""CampaignContacts"", fields: [A], references: [id]) // contact Contact @relation(""CampaignContacts"", fields: [B], references: [id]) // @@index([A]) // @@index([B]) // }
- Explanation:
- We define
Contact
with essential fields likephoneNumber
. Storing in E.164 format (e.g.,+15551234567
) is highly recommended. - We define
Campaign
with a name, message body, status, andsinchBatchId
to store the ID returned by the Sinch Batch SMS API for tracking. - An index is added to
Campaign.status
for performance. - Relations (like linking contacts to campaigns) can be added based on complexity.
- We define
- Explanation:
-
Apply Database Migrations: Use the Redwood CLI to create and apply a database migration based on the schema changes.
yarn rw prisma migrate dev --name create_contacts_campaigns
- This command:
- Creates a new SQL migration file in
api/db/migrations/
. - Applies the migration to your development database.
- Generates/updates the Prisma Client based on your schema.
- Creates a new SQL migration file in
- This command:
-
Generate Services: Use the Redwood CLI to generate boilerplate code for services that will handle the business logic for contacts and campaigns.
yarn rw g service contact yarn rw g service campaign
- This creates files like
api/src/services/contacts/contacts.ts
,api/src/services/campaigns/campaigns.ts
, and corresponding test/scenario files.
- This creates files like
-
Implement Service Logic (Basic CRUD): Open the generated service files and add basic CRUD (Create, Read, Update, Delete) operations.
api/src/services/contacts/contacts.ts
:
import type { QueryResolvers, MutationResolvers } from 'types/graphql' import { db } from 'src/lib/db' import { requireAuth } from 'src/lib/auth' // Assuming auth will be set up export const contacts: QueryResolvers['contacts'] = () => { requireAuth() return db.contact.findMany() } export const contact: QueryResolvers['contact'] = ({ id }) => { requireAuth() return db.contact.findUnique({ where: { id }, }) } interface CreateContactInput { phoneNumber: string // Add validation for E.164 format here or in GraphQL layer firstName?: string lastName?: string } export const createContact: MutationResolvers['createContact'] = ({ input }: { input: CreateContactInput }) => { requireAuth() // TODO: Add validation for phoneNumber format (E.164) return db.contact.create({ data: input, }) } // Add updateContact and deleteContact if needed
api/src/services/campaigns/campaigns.ts
:
import type { QueryResolvers, MutationResolvers } from 'types/graphql' import { db } from 'src/lib/db' import { requireAuth } from 'src/lib/auth' // Assuming auth is set up // We will import the Sinch client later // import { sendSinchSmsBatch } from 'src/lib/sinchClient' export const campaigns: QueryResolvers['campaigns'] = () => { requireAuth() return db.campaign.findMany({ orderBy: { createdAt: 'desc' } }) } export const campaign: QueryResolvers['campaign'] = ({ id }) => { requireAuth() return db.campaign.findUnique({ where: { id }, }) } interface CreateCampaignInput { name: string messageBody: string } export const createCampaign: MutationResolvers['createCampaign'] = ({ input }: { input: CreateCampaignInput }) => { requireAuth() // Just create in DB for now. Sending logic will be added later. return db.campaign.create({ data: { ...input, status: 'DRAFT' }, }) } // Placeholder for the sending action (Implementation in Section 4) export const sendCampaign = async ({ id }: { id: number }) => { requireAuth() console.log(`Placeholder: Logic to send campaign ${id} via Sinch would go here.`) // 1. Fetch campaign details from DB // 2. Fetch target contacts from DB (e.g., all active contacts) // 3. Call the Sinch API client (to be built in Section 4) // 4. Update campaign status in DB (e.g., SENDING, SENT, FAILED) // 5. Store Sinch Batch ID const tempCampaign = await db.campaign.findUnique({ where: { id } }); // Fetch for return type consistency return { success: true, message: `Campaign ${id} sending initiated (placeholder).`, campaign: tempCampaign } } // Add updateCampaign and deleteCampaign if needed
- Why Services? Services encapsulate business logic, making the code modular, testable, and reusable. They act as the bridge between the GraphQL API layer and the database/external services.
-
Data Layer Summary:
-
ERD (Simplified):
+-------------+ +--------------+ | Contact | | Campaign | |-------------| |--------------| | PK id | | PK id | | phoneNumber | | name | | firstName | | messageBody | | lastName | | status | | createdAt | | sinchBatchId?| | updatedAt | | scheduledAt? | | | | sentAt? | | | | createdAt | | | | updatedAt | +-------------+ +--------------+ | | (Many-to-Many possible but not shown)
-
Data Access: Handled by Prisma Client via Redwood services (
db.campaign.findMany()
,db.contact.create()
, etc.). Redwood'sdb
object is an instance ofPrismaClient
. -
Migrations: Managed by
yarn rw prisma migrate dev
. For production, useyarn rw prisma migrate deploy
.
-
-
Seed Sample Data (Optional): Use Prisma seeds to populate your development database.
-
Create seed file:
touch api/db/seed.ts
-
Add seed logic:
// api/db/seed.ts import { PrismaClient } from '@prisma/client' const db = new PrismaClient() async function main() { console.log(`Start seeding ...`) // Seed Contacts await db.contact.upsert({ where: { phoneNumber: '+15551112222' }, update: {}, create: { phoneNumber: '+15551112222', firstName: 'Alice' }, // Use valid E.164 formats }) await db.contact.upsert({ where: { phoneNumber: '+15553334444' }, update: {}, create: { phoneNumber: '+15553334444', firstName: 'Bob', lastName: 'Smith' }, }) console.log('Seeded contacts.') // Seed Campaigns await db.campaign.upsert({ where: { name: 'Welcome Campaign Draft' }, // Use a unique field if possible update: {}, create: { name: 'Welcome Campaign Draft', messageBody: 'Welcome to our service!', status: 'DRAFT' } }) console.log('Seeded campaigns.') console.log(`Seeding finished.`) } main() .catch((e) => { console.error(e) process.exit(1) }) .finally(async () => { await db.$disconnect() })
-
Add seed script to
api/package.json
(check if Redwood added it first):// api/package.json (inside ""scripts"") ""prisma:seed"": ""prisma db seed""
And ensure
prisma.seed
is set inapi/db/schema.prisma
generator block:// api/db/schema.prisma generator client { provider = ""prisma-client-js"" // Add or ensure this line exists: // previewFeatures = [""jsonProtocol""] // Example, might already exist } // Ensure this block exists in schema.prisma (often added by Redwood) // datasource db { ... } // generator client { ... } // Add prisma seed configuration in package.json if needed
Check your
api/package.json
first, Redwood often configuresprisma db seed
. -
Configure the seed command in
prisma
section of rootpackage.json
if not present:// package.json (root) ""prisma"": { ""seed"": ""yarn rw exec seed"" }
-
Run seeding:
yarn rw prisma db seed
-
3. Building the API Layer (GraphQL SDL)
RedwoodJS automatically maps service functions to GraphQL resolvers. We just need to define the schema.
-
Generate GraphQL SDL: Use the Redwood CLI to generate SDL files based on your Prisma schema models.
yarn rw g sdl contact --crud yarn rw g sdl campaign --crud
--crud
: Generates standard GraphQL types, queries (for reading), and mutations (for creating, updating, deleting) based on the model and corresponding service functions.
-
Review and Customize SDL: Inspect the generated files (
api/src/graphql/contacts.sdl.ts
andapi/src/graphql/campaigns.sdl.ts
). They should look similar to this (customize as needed):api/src/graphql/contacts.sdl.ts
:
export const schema = gql` type Contact { id: Int! phoneNumber: String! firstName: String lastName: String createdAt: DateTime! updatedAt: DateTime! } type Query { contacts: [Contact!]! @requireAuth contact(id: Int!): Contact @requireAuth } input CreateContactInput { phoneNumber: String! # Consider adding scalar type for E.164 validation firstName: String lastName: String } input UpdateContactInput { phoneNumber: String firstName: String lastName: String } type Mutation { createContact(input: CreateContactInput!): Contact! @requireAuth # updateContact and deleteContact mutations will also be generated if using --crud # updateContact(id: Int!, input: UpdateContactInput!): Contact! @requireAuth # deleteContact(id: Int!): Contact! @requireAuth } `
api/src/graphql/campaigns.sdl.ts
:
export const schema = gql` type Campaign { id: Int! name: String! messageBody: String! status: String! sinchBatchId: String # Renamed field scheduledAt: DateTime sentAt: DateTime createdAt: DateTime! updatedAt: DateTime! } type Query { campaigns: [Campaign!]! @requireAuth campaign(id: Int!): Campaign @requireAuth } input CreateCampaignInput { name: String! messageBody: String! } input UpdateCampaignInput { name: String messageBody: String status: String sinchBatchId: String # Renamed field scheduledAt: DateTime sentAt: DateTime } # Add a mutation for our custom send action type SendCampaignResponse { success: Boolean! message: String campaign: Campaign # Return updated campaign } type Mutation { createCampaign(input: CreateCampaignInput!): Campaign! @requireAuth # updateCampaign, deleteCampaign mutations... # updateCampaign(id: Int!, input: UpdateCampaignInput!): Campaign! @requireAuth # deleteCampaign(id: Int!): Campaign! @requireAuth sendCampaign(id: Int!): SendCampaignResponse! @requireAuth # Custom mutation } ` * **`@requireAuth`:** Redwood's directive to enforce authentication. We'll keep it simple for now, but in a real app, you'd set up Redwood Auth (`yarn rw setup auth ...`). You can remove `@requireAuth` temporarily during development if auth isn't set up yet, but **remember to add it back**. * **`SendCampaignResponse` & `sendCampaign` Mutation:** We added a custom mutation (`sendCampaign`) and a response type to trigger our campaign sending logic in the service.
-
Testing API Endpoints (GraphQL Playground):
-
Start the development server:
yarn rw dev
-
Open your browser to
http://localhost:8910/graphql
(or the port specified in the console output). -
You can now test your queries and mutations.
-
Example Query (Get Campaigns):
query GetCampaigns { campaigns { id name status createdAt sinchBatchId # Added field } }
-
Example Mutation (Create Campaign):
mutation CreateNewCampaign { createCampaign(input: { name: "May Promo Blast" messageBody: "Hey {firstName}, check out our May deals! Limited time only." }) { id name messageBody status } }
-
(Note:
@requireAuth
might block requests if auth isn't configured. Remove it from the SDL temporarily for testing if needed, but ensure it's present for production.)
-
4. Integrating with Sinch API
Now, let's build the client to communicate with the actual Sinch Batch SMS API.
-
Create Sinch Client Library: Create a new file for our Sinch API interaction logic.
mkdir -p api/src/lib/sinchClient
touch api/src/lib/sinchClient/index.ts
-
Implement Sinch API Client Logic: Open
api/src/lib/sinchClient/index.ts
and add the code to interact with Sinch.// api/src/lib/sinchClient/index.ts import fetch from 'node-fetch' // Using node-fetch v2 const SINCH_SERVICE_PLAN_ID = process.env.SINCH_SERVICE_PLAN_ID const SINCH_API_TOKEN = process.env.SINCH_API_TOKEN const SINCH_API_BASE_URL = process.env.SINCH_API_BASE_URL || 'https://us.sms.api.sinch.com' // Default fallback interface SinchAPIErrorResponse { request_id?: string; // Optional based on Sinch error format error?: { code: number; message: string; reference?: string; }; // Batch specific errors might have different formats text_code?: string; text_message?: string; } // Helper function to make authenticated requests to Sinch API async function sinchRequest<T>( endpoint: string, method: 'GET' | 'POST' | 'PUT' | 'DELETE', body?: Record<string_ any> ): Promise<T> { if (!SINCH_SERVICE_PLAN_ID || !SINCH_API_TOKEN) { throw new Error('Sinch API credentials (SINCH_SERVICE_PLAN_ID, SINCH_API_TOKEN) are not configured in .env') } const url = `${SINCH_API_BASE_URL}/xms/v1/${SINCH_SERVICE_PLAN_ID}${endpoint}` const headers = { 'Authorization': `Bearer ${SINCH_API_TOKEN}`, 'Content-Type': 'application/json', 'Accept': 'application/json', } console.log(`Sinch Request: ${method} ${url}`) // Basic logging try { const response = await fetch(url, { method: method, headers: headers, body: body ? JSON.stringify(body) : undefined, }) if (!response.ok) { // Attempt to parse error details from Sinch, which can vary let errorDetails: string | SinchAPIErrorResponse = await response.text(); let errorMessage = response.statusText; try { const parsedError = JSON.parse(errorDetails) as SinchAPIErrorResponse; console.error('Sinch API Error Response:', parsedError); // Try different common error message fields errorMessage = parsedError.error?.message || parsedError.text_message || errorMessage; throw new Error(`Sinch API Error (${response.status}): ${errorMessage}. Ref: ${parsedError.error?.reference || parsedError.request_id || 'N/A'}`); } catch (parseError) { // If parsing fails, use the raw text console.error('Sinch API Error Response (non-JSON or parse error):', errorDetails); throw new Error(`Sinch API Error (${response.status}): ${response.statusText}. Response: ${errorDetails}`); } } // Handle cases where Sinch might return 204 No Content or similar success without body if (response.status === 204 || response.headers.get('content-length') === '0') { // Return an empty object or specific success indicator if needed // For batch send (POST /batches), Sinch *does* return a body on success (200 or 201) return {} as T; // Adjust as needed for other endpoints } // Assuming successful responses are JSON const data = await response.json() as T console.log(`Sinch Response (${response.status}):`, data) // Basic logging return data; } catch (error) { console.error(`Error during Sinch API request to ${endpoint}:`, error) // Re-throw a more specific error or handle appropriately // Avoid exposing raw Sinch errors directly to frontend if possible throw new Error(`Failed to communicate with Sinch API: ${error.message}`); } } // --- Specific Sinch API Functions --- interface SendSMSInput { to: string[]; // Array of phone numbers in E.164 format from: string; // Your Sinch virtual number, Short Code, or Alphanumeric Sender ID body: string; // The message content // Common optional parameters for batch SMS: delivery_report?: 'none' | 'summary' | 'full'; // default 'none' client_reference?: string; // Your custom ID for tracking parameters?: Record<string_ { default?: string; [phoneNumber: string]: string }>; // For message templating/personalization // See: https://developers.sinch.com/docs/sms/api-reference/sms/tag/Batches/#tag/Batches/operation/SendSMS } interface SendSMSResponse { id: string; // The Batch ID returned by Sinch to: string[]; from: string; canceled: boolean; body: string; type: string; // e.g., ""mt_batch"" created_at: string; // ISO 8601 date string modified_at: string; // ISO 8601 date string delivery_report: string; // ... other fields like 'send_at', 'expire_at', 'flash_message', 'feedback_enabled' etc. } /** * Sends an SMS batch message using the Sinch API. * This function uses the standard Sinch Batch SMS endpoint (`/xms/v1/{SERVICE_PLAN_ID}/batches`). * While suitable for sending marketing messages, check Sinch documentation for any dedicated * ""Marketing Campaign"" APIs if you need features beyond batch sending (e.g., advanced list management). */ export async function sendSinchSmsBatch(input: SendSMSInput): Promise<SendSMSResponse> { // Input validation (basic) if (!input.to || input.to.length === 0) { throw new Error('Recipient list (`to`) cannot be empty.'); } if (!input.from) { throw new Error('Sender ID (`from`) is required.'); } if (!input.body) { throw new Error('Message body (`body`) is required.'); } // TODO: Add more robust validation (e.g., E.164 format for 'to' numbers, length checks for 'body') const endpoint = '/batches' // Endpoint for sending batches return sinchRequest<SendSMSResponse>(endpoint, 'POST', { to: input.to, from: input.from, body: input.body, delivery_report: input.delivery_report || 'summary', // Request a summary DLR client_reference: input.client_reference, // Pass through client reference if provided parameters: input.parameters, // Pass through parameters if provided }) } // Add other functions as needed, e.g., getBatchStatus(batchId), cancelBatch(batchId), etc. // Consult the Sinch SMS API documentation for the correct endpoints and payloads.
- Explanation:
- Reads credentials and base URL from
process.env
. - Includes a
sinchRequest
helper function to handle authentication, headers, request/response logging, and basic error handling for all Sinch API calls. It usesnode-fetch
. - Provides
sendSinchSmsBatch
to interact with the/batches
endpoint, suitable for sending messages to multiple recipients. - Includes comments clarifying the API endpoint used and suggesting users check for specific Marketing Campaign APIs if needed.
- Includes basic input validation and improved error parsing.
- API Key Security: Credentials are read from environment variables, not hardcoded.
- Reads credentials and base URL from
- Explanation:
-
Obtaining Sinch Credentials:
- Log in to your Sinch Customer Dashboard.
- Navigate to the SMS -> APIs section (or similar).
- Find your Service Plan ID.
- Under API Credentials, generate or copy an API Token.
- Note your registered Sender ID (under Numbers or Sender IDs). This must be approved for sending.
- Copy these values into your
.env
file. - Confirm the correct API Base URL for your account's region (e.g.,
us.sms.api.sinch.com
,eu.sms.api.sinch.com
).
-
Connect Sinch Client to Campaign Service: Now, update the
sendCampaign
function inapi/src/services/campaigns/campaigns.ts
to use the Sinch client.// api/src/services/campaigns/campaigns.ts import type { QueryResolvers, MutationResolvers, CampaignResolvers } from 'types/graphql' // Added CampaignResolvers import { db } from 'src/lib/db' import { sendSinchSmsBatch } from 'src/lib/sinchClient' // ... (rest of the imports and existing code like campaigns, campaign, createCampaign) ...