Delphi SMS Sender Tutorial — From Setup to Delivery ReportsThis tutorial walks you through building a robust Delphi SMS sender: setting up your environment, choosing an SMS gateway, implementing send/receive logic, handling delivery reports, retry and error strategies, security considerations, and testing tips. It targets Delphi 10.x (Tokyo and later) with FireMonkey (cross-platform) or VCL for Windows. Examples use modern Indy (TIdHTTP) or native REST client components and demonstrate both synchronous and asynchronous patterns.
1. Overview and prerequisites
Sending SMS from a Delphi application typically involves:
- Integrating with an SMS gateway provider (HTTP REST API, SMPP, or SMTP-to-SMS).
- Implementing HTTP requests or SMPP client logic.
- Tracking message IDs and processing delivery reports (DLRs) returned via callbacks or polling.
- Logging, retrying failures, and ensuring secure credential handling.
Prerequisites:
- Delphi 10.x or later (examples use Delphi 10.3+ syntax). VCL for Windows or FireMonkey for cross-platform.
- An SMS gateway account (examples assume a generic REST API — adjust to your provider).
- Basic familiarity with HTTP requests, JSON, and threading.
2. Choosing an SMS gateway
Common gateway options:
- HTTP REST API: simplest for most apps; provider returns message IDs and supports callbacks for DLRs.
- SMPP: lower-level protocol for high throughput; requires an SMPP client library.
- SMTP-to-SMS: unreliable and limited — avoid for production.
Key selection criteria:
- API documentation and SDKs
- Delivery speed and SLA
- Support for concatenated (long) SMS, Unicode, and binary data
- Delivery report (DLR) support and DLR callback mechanisms
- Pricing, throughput limits, and regional coverage
- Security: API keys, IP allowlisting, TLS
3. Authentication & configuration
Most providers use API keys or HTTP Basic Auth. Keep credentials out of source control:
- Use encrypted config files, OS keychains, or environment variables.
- Configure TLS (HTTPS) endpoints only.
- Optionally restrict API key by IP.
Example configuration structure (pseudo-JSON):
{ "api_base": "https://api.smsprovider.com/v1", "api_key": "REDACTED", "sender_id": "MyApp", "max_retries": 3, "timeout_seconds": 30 }
4. Basic sending using HTTP REST (synchronous)
This example shows a simple synchronous send using Indy (TIdHTTP) and System.JSON. For clarity, adjust exception handling and logging for production.
uses IdHTTP, IdSSL, IdSSLOpenSSL, System.JSON, System.Classes, System.SysUtils; function SendSMS_Sync(const BaseURL, ApiKey, FromNum, ToNum, MessageText: string): string; var http: TIdHTTP; ssl: TIdSSLIOHandlerSocketOpenSSL; reqJson, respJson: TJSONObject; respStr: TStringStream; url: string; begin http := TIdHTTP.Create(nil); ssl := TIdSSLIOHandlerSocketOpenSSL.Create(nil); respStr := TStringStream.Create('', TEncoding.UTF8); reqJson := nil; respJson := nil; try http.IOHandler := ssl; http.Request.ContentType := 'application/json'; http.Request.CustomHeaders.Values['Authorization'] := 'Bearer ' + ApiKey; url := BaseURL + '/messages'; reqJson := TJSONObject.Create; reqJson.AddPair('from', FromNum); reqJson.AddPair('to', ToNum); reqJson.AddPair('text', MessageText); http.Post(url, TStringStream.Create(reqJson.ToString, TEncoding.UTF8), respStr); respJson := TJSONObject.ParseJSONValue(respStr.DataString) as TJSONObject; // Assume provider returns {"message_id": "..."} Result := respJson.GetValue('message_id').Value; finally respJson.Free; reqJson.Free; respStr.Free; ssl.Free; http.Free; end; end;
Notes:
- Synchronous calls block the calling thread — use in background threads or for simple CLI/tools.
- Parse and store the returned message_id for future delivery report correlation.
5. Asynchronous sending (non-blocking)
Use TTask (System.Threading) or background threads to avoid UI freeze.
uses System.Threading, System.Classes, System.SysUtils; procedure SendSMS_Async(const BaseURL, ApiKey, FromNum, ToNum, MessageText: string; Callback: TProc<string, Exception>); begin TTask.Run( procedure var msgId: string; ex: Exception; begin ex := nil; try msgId := SendSMS_Sync(BaseURL, ApiKey, FromNum, ToNum, MessageText); except on E: Exception do begin ex := E; msgId := ''; end; end; if Assigned(Callback) then TThread.Queue(nil, procedure begin Callback(msgId, ex); end ); end ); end;
6. Handling message encoding and length
- SMS default encoding is GSM 03.38. For characters outside GSM (e.g., Cyrillic, emoji) use UCS-2 (Unicode) which reduces per-segment payload.
- Typical per-segment lengths:
- GSM 7-bit: 160 chars (153 if concatenated)
- UCS-2: 70 chars (67 if concatenated)
- Providers often accept a flag or auto-detect encoding; include a “udh” or “encoding” parameter if needed.
7. Delivery reports (DLRs)
Delivery reports inform you whether a message reached the handset. Two common patterns:
- Callback/webhook: Provider performs HTTP POST to your endpoint with message_id, status, timestamp. Secure with IP allowlist, HMAC signature, or a token.
- Polling: Your app queries the provider’s status endpoint using message_id.
Webhook example (expected JSON payload):
{ "message_id": "abc123", "to": "+1234567890", "status": "delivered", "timestamp": "2025-08-29T12:34:56Z", "provider_status": "DELIVRD" }
Server-side minimal webhook handler (Delphi using WebBroker or an MVC framework):
procedure TWebModule.SMSWebhookAction(Request: TWebRequest; Response: TWebResponse); var body, msgId, status: string; json: TJSONObject; begin body := Request.Content; json := TJSONObject.ParseJSONValue(body) as TJSONObject; try msgId := json.GetValue('message_id').Value; status := json.GetValue('status').Value; // Update DB: message status, timestamp, provider-specific fields Response.StatusCode := 200; Response.Content := 'OK'; finally json.Free; end; end;
Security for webhooks:
- Validate an HMAC signature header computed with your shared secret.
- Verify the request IP against provider IPs or use mutual TLS if supported.
- Return 200 quickly; perform heavy processing asynchronously.
8. Correlation and storage model
Keep a local table for messages:
Columns:
- id (local UUID)
- provider_message_id (string)
- to_number (string)
- from_number (string)
- text (string)
- encoding (string)
- status (queued/sent/failed/delivered/undelivered)
- sent_at (datetime)
- status_at (datetime)
- retries (int)
- last_error (string)
Use provider_message_id to match incoming DLRs to local records. Always log full provider payload for troubleshooting.
9. Retries, rate limiting, and backoff
- Apply exponential backoff for transient failures (HTTP 429, 5xx). Example backoff: base 2s, multiply by 2, cap at 1 minute.
- Respect provider rate limits; implement client-side throttling (token bucket).
- For permanent failures (invalid number, blacklisted), mark as failed and surface to users.
Example pseudo-code for retry:
retry := 0; repeat try Send(); Break; except on E: Exception do begin Inc(retry); if retry > MaxRetries then raise; Sleep(Min(60000, 2000 * (1 shl retry))); // exponential with cap end; end; until False;
10. SMPP option (high throughput)
If you need high throughput and lower latency, SMPP is common. Delphi lacks built-in SMPP; choose a library:
- Commercial or open-source SMPP clients exist — evaluate for maintenance and features.
- SMPP requires more careful session and connection management (enquire_link, bind/unbind, submit_sm, deliver_sm).
- SMPP supports TLVs and binary short message payloads (for concatenation and Unicode).
11. Monitoring & observability
- Track metrics: messages sent, delivered, failed, average latency, and DLR lag.
- Log request/response payloads (mask credentials).
- Alert on rising failure rate or delivery delays.
- Provide an administrative UI to resend, view raw DLRs, or export logs.
12. Security & compliance
- Use HTTPS; rotate API keys regularly.
- Mask/stash PII (phone numbers) per your privacy requirements.
- Ensure opt-in/opt-out handling conforms to local regulations (TCPA, GDPR).
- Store consent timestamps and message templates for audit.
13. Testing tips
- Use provider’s sandbox/test mode when possible.
- Test edge cases: unicode messages, long concatenated messages, invalid numbers, and DLR latencies.
- Simulate webhooks locally with tools like ngrok or a staging endpoint.
- Load-test with realistic rate limits to expose throttling issues.
14. Example full flow (summary)
- User composes message in app.
- App posts send request to your backend.
- Backend validates, stores message row (status = queued).
- Backend sends HTTP request to SMS provider; stores provider_message_id and sets status = sent (or failed).
- Provider delivers SMS; sends a DLR to your webhook or you poll status.
- Webhook updates local status to delivered/undelivered and logs provider metadata.
- Retry or manual intervention if undelivered.
15. Troubleshooting common issues
- No DLRs: verify webhook URL, firewall, or provider configuration; check webhook logs.
- Messages stuck in queued: inspect retry logic and provider error responses.
- Incorrect encoding: ensure provider encoding flags or convert text to UCS-2 when needed.
- High latency: check network, provider SLA, and throttling responses (429).
16. Libraries & tools
- Indy (TIdHTTP) — widely available HTTP client.
- REST.Client / REST.Response.Adapter — Delphi REST components for simpler implementations.
- Third-party SMPP clients — search for actively maintained libraries.
- ngrok/localtunnel — for webhook testing.
17. Sample checklist before going to production
- Use account sandbox then production credentials.
- Enforce TLS and secret management.
- Configure webhook security (HMAC/IP whitelist).
- Implement retries and rate limiting.
- Log and monitor DLRs and failure rates.
- Confirm opt-in/opt-out and legal compliance.
This guide gives you a practical path from initial setup through delivery reporting and production readiness. If you want, I can: provide a complete demo project for VCL or FireMonkey, create a webhook verification snippet with HMAC, or show an SMPP client integration example. Which would you like next?
Leave a Reply