UC-006 — Bulk SMS Campaign to Thousands of Recipients
| Field | Value |
|---|---|
| ID | UC-006 |
| Goal | Create and send a bulk SMS campaign with cost estimation and monitoring |
| Channel | SMS |
| Complexity | Intermediate |
| Estimated time | 20 minutes |
| APIs involved | POST /api/partner-gateway/v1/campaigns, PUT /campaigns/{id}, POST /campaigns/{id}/calculateGoal, GET /campaigns/{id}/price, PUT /campaigns/{id}/confirm, GET /campaigns/stats |
Real-world scenarios
- ShopItalia — Black Friday: Flash promotion to 10,000 customers with a personalized discount code and a 48-hour expiration. Immediate send on Friday morning at 08:00.
- Studio Dentistico Bianchi — Weekly reminders: Every Monday the system sends a reminder of the week's appointments to all booked patients.
- AcquaLuce Energia — Bill due date: Payment due notice to all customers with unpaid invoices, with a link to the payment portal.
Prerequisites
Before you begin, make sure you have:
- Active API Key → How to get one
- Sufficient credit → Check in the Qlara Dashboard
- Contact list created and populated → See UC-007
:::tip Test without costs
Add "simulation": true in the request body to validate the flow without actually sending messages and without consuming credit.
:::
Campaign lifecycle
The diagram illustrates the state transitions: from creation (Draft) to configuration, cost estimation, confirmation, and finally sending.
Step 1 — Create the campaign (Draft)
Create a new campaign in draft state specifying name, channel, and description.
curl -X POST https://lora-api.agiletelecom.com/api/partner-gateway/v1/campaigns \
-H "Content-Type: application/json" \
-H "X-Api-Key: YOUR_API_KEY" \
-d '{
"name": "Black Friday 2026",
"channel": "SMS",
"description": "Promozione Black Friday - sconto 30% su tutto il catalogo"
}'
Response — Campaign created
{
"id": 1542,
"name": "Black Friday 2026",
"channel": "SMS",
"description": "Promozione Black Friday - sconto 30% su tutto il catalogo",
"status": "DRAFT",
"createdAt": "2026-04-09T09:00:00Z"
}
:::tip Save the campaign ID
The id field is the identifier you will use in all subsequent steps. Save it to track the campaign.
:::
Step 2 — Configure message and recipients (Configured)
Associate the contact list, sender, and message text with the campaign.
curl -X PUT https://lora-api.agiletelecom.com/api/partner-gateway/v1/campaigns/1542 \
-H "Content-Type: application/json" \
-H "X-Api-Key: YOUR_API_KEY" \
-d '{
"sender": "ShopItalia",
"text": "Black Friday! Solo per te: -30% su tutto con il codice BF2026. Valido fino al 30/11. Scopri di più: https://shop.example.it/bf",
"contactListId": 87,
"unicode": false
}'
Response — Campaign configured
{
"id": 1542,
"name": "Black Friday 2026",
"channel": "SMS",
"status": "CONFIGURED",
"sender": "ShopItalia",
"text": "Black Friday! Solo per te: -30% su tutto con il codice BF2026. Valido fino al 30/11. Scopri di più: https://shop.example.it/bf",
"contactListId": 87,
"unicode": false,
"updatedAt": "2026-04-09T09:02:00Z"
}
:::info Contact list
The contactListId must refer to an already created list. See UC-007 — Manage Contacts and Lists to create and populate lists.
:::
Step 3 — Calculate cost (CostCalculated)
Before confirming, verify how many valid recipients there are and how many SMS parts will be needed.
curl -X POST https://lora-api.agiletelecom.com/api/partner-gateway/v1/campaigns/1542/calculateGoal \
-H "X-Api-Key: YOUR_API_KEY"
Response — Cost calculated
{
"id": 1542,
"status": "COST_CALCULATED",
"totalRecipients": 10247,
"validRecipients": 9831,
"invalidRecipients": 416,
"estimatedParts": 9831,
"calculatedAt": "2026-04-09T09:03:00Z"
}
Behind the scenes — Recipient validation
During calculation, the system performs these operations:
- Deduplication: Removes duplicate numbers present in the list.
- Format validation: Discards numbers not in international format or with incorrect syntax.
- Part estimation: Analyzes the text to determine the number of SMS per message (1 part = 160 char GSM-7, 70 char UCS-2).
- Blacklist: Excludes numbers present in opt-out or system blacklists.
The 416 invalid recipients in this example include duplicate, malformed, or blacklisted numbers.
Step 4 — Check the price
Check the total cost in credits before proceeding with confirmation.
curl -X GET https://lora-api.agiletelecom.com/api/partner-gateway/v1/campaigns/1542/price \
-H "X-Api-Key: YOUR_API_KEY"
Response — Estimated price
{
"id": 1542,
"totalRecipients": 9831,
"pricePerMessage": 0.035,
"totalPrice": 344.09,
"currency": "EUR",
"availableCredit": 1500.00,
"sufficient": true
}
:::warning Insufficient credit
If sufficient is false, confirmation will fail. Top up your credit before proceeding.
:::
Step 5 — Confirm and start sending
Confirm the campaign to start immediate sending.
curl -X PUT https://lora-api.agiletelecom.com/api/partner-gateway/v1/campaigns/1542/confirm \
-H "Content-Type: application/json" \
-H "X-Api-Key: YOUR_API_KEY" \
-d '{
"sendImmediately": true
}'
Response — Campaign confirmed
{
"id": 1542,
"status": "CONFIRMED",
"sendImmediately": true,
"confirmedAt": "2026-04-09T09:05:00Z"
}
Variant — Scheduled send
To schedule sending at a future date, use scheduledDate instead of sendImmediately:
curl -X PUT https://lora-api.agiletelecom.com/api/partner-gateway/v1/campaigns/1542/confirm \
-H "Content-Type: application/json" \
-H "X-Api-Key: YOUR_API_KEY" \
-d '{
"scheduledDate": "2026-11-29T08:00:00.000+0000"
}'
Step 6 — Monitor progress
Check delivery statistics in real time during and after sending.
curl -X GET "https://lora-api.agiletelecom.com/api/partner-gateway/v1/campaigns/stats?campaignId=1542" \
-H "X-Api-Key: YOUR_API_KEY"
Response — Campaign completed
{
"campaignId": 1542,
"name": "Black Friday 2026",
"status": "COMPLETED",
"stats": {
"total": 9831,
"sent": 9831,
"delivered": 9542,
"undelivered": 289,
"pending": 0,
"deliveryRate": 97.06
},
"cost": {
"totalCredits": 344.09,
"currency": "EUR"
},
"completedAt": "2026-04-09T09:47:00Z"
}
Final result: 9,542 messages delivered out of 9,831 valid recipients (97.06%), total cost 344.09 EUR.
Common errors
400 Bad Request — Contact list not configured
{
"status": "fail",
"data": {
"contactListId": "Contact list is required before calculating cost"
}
}
Solution: Make sure you have completed Step 2 (configuration) before requesting cost calculation.
402 — Insufficient credit
{
"status": "fail",
"data": {
"credit": "Insufficient credit. Required: 344.09, available: 120.00"
}
}
Solution: Top up your credit from the platform dashboard before confirming the campaign.
Expected result
| Step | Action | Result |
|---|---|---|
| 1 | POST /campaigns | Campaign in DRAFT state, id returned |
| 2 | PUT /campaigns/{id} | State CONFIGURED, text and list associated |
| 3 | POST /campaigns/{id}/calculateGoal | State COST_CALCULATED, recipients validated |
| 4 | GET /campaigns/{id}/price | Total cost and sufficient credit check |
| 5 | PUT /campaigns/{id}/confirm | State CONFIRMED -> SENDING |
| 6 | GET /campaigns/stats | Real-time delivery statistics |
Next steps
- UC-007 — Manage Contacts and Lists: Create and populate contact lists for your campaigns
- UC-008 — Delivery Tracking with Webhooks: Receive real-time delivery notifications
- Campaigns Guide: Deep dive into scheduling, filters, and advanced management