Delphi SMS Client: Best Practices and Sample Code
Introduction
Sending SMS from Delphi applications remains a common requirement for notifications, two-factor authentication, and alerting systems. This article presents best practices for building a reliable, secure Delphi SMS client and provides compact sample code that demonstrates common workflows: sending an SMS via an HTTP-based SMS gateway, handling responses, and retrying failed deliveries.
Best Practices
1. Choose the right SMS gateway
- Reliability: Pick a provider with high deliverability and SLAs.
- API style: Prefer REST/HTTP JSON APIs over legacy protocols (SMPP) unless you need high throughput.
- Coverage & cost: Ensure the provider supports the target countries and offers predictable pricing.
- Delivery reports: Use gateways that provide delivery receipts (DLR) so you can confirm message status.
2. Secure credentials and configuration
- Store secrets safely: Do not hard-code API keys. Use secure configuration (environment variables, encrypted config files).
- Use HTTPS: Always call the gateway over TLS.
- Rotate keys: Rotate API keys regularly and remove unused credentials.
3. Respect rate limits and quotas
- Throttle requests: Implement client-side rate limiting to avoid being throttled or blocked.
- Queueing: Use a queue (in-memory or persistent) for bursts. Process items at a controlled rate.
4. Implement robust error handling and retries
- Differentiate errors: Retry on transient network or 5xx errors; do not retry on permanent 4xx errors (e.g., invalid number, unauthorized).
- Backoff strategy: Use exponential backoff with jitter for retries.
- Idempotency: Include a client-generated message ID where supported to avoid duplicate deliveries on retries.
5. Validate and format phone numbers
- E.164 format: Normalize numbers to E.164 before sending.
- Local rules: Apply country-specific validation to reduce provider rejections.
6. Monitor and log
- Structured logs: Record API requests, responses, and delivery receipts (without logging secrets).
- Metrics & alerts: Track success/failure rates, latency, and queue sizes. Alert on spikes in failures.
7. Handle delivery receipts and inbound messages
- Webhook endpoint: Implement a secure webhook to receive DLRs and inbound messages; validate payloads (HMAC signature or token).
- Idempotent processing: Ensure webhook handling is idempotent.
8. Cost control and user experience
- Rate limits per user: Apply per-user rate limits to avoid abuse.
- Message length & encoding: Be aware of concatenation and Unicode costs; prefer GSM-7 where possible.
Sample Code (Delphi, HTTP JSON REST)
Below is a concise example showing:
- Sending an SMS via a REST gateway (POST JSON)
- Basic response handling
- Simple retry with exponential backoff
Note: Replace placeholders (API_URL, APIKEY) with your provider values. This example uses Indy (TIdHTTP) and System.JSON.
pascal
uses System.SysUtils, System.Classes, System.JSON, IdHTTP, IdSSLOpenSSL, IdGlobal; const API_URL = ‘https://api.example-sms.com/v1/messages’; API_KEY = ‘YOUR_API_KEY’; // store securely, not in source type ESmsError = class(Exception); function PostJSON(const AUrl, AJson, AApiKey: string; out AResponse: string): Integer; var IdHTTP: TIdHTTP; SSLHandler: TIdSSLIOHandlerSocketOpenSSL; RequestStream, ResponseStream: TStringStream; begin IdHTTP := TIdHTTP.Create(nil); SSLHandler := TIdSSLIOHandlerSocketOpenSSL.Create(IdHTTP); RequestStream := TStringStream.Create(AJson, TEncoding.UTF8); ResponseStream := TStringStream.Create(“, TEncoding.UTF8); try IdHTTP.IOHandler := SSLHandler; IdHTTP.Request.ContentType := ‘application/json; charset=utf-8’; IdHTTP.Request.CustomHeaders.Values[‘Authorization’] := ‘Bearer ‘ + AApiKey; try IdHTTP.Post(AUrl, RequestStream, ResponseStream); AResponse := ResponseStream.DataString; Result := IdHTTP.ResponseCode; except on E: EIdHTTPProtocolException do begin AResponse := E.ErrorMessage; Result := E.ErrorCode; Exit; end; on E: Exception do begin raise; end; end; finally IdHTTP.Free; SSLHandler.Free; RequestStream.Free; ResponseStream.Free; end; end; function SendSMS(const AToE164, ABody, AClientMsgId: string): string; var JsonObj: TJSONObject; JsonStr, Resp: string; Code, Attempt: Integer; BackoffMs: Integer; begin // Build JSON payload JsonObj := TJSONObject.Create; try JsonObj.AddPair(‘to’, AToE164); JsonObj.AddPair(‘body’, ABody); if AClientMsgId <> ” then JsonObj.AddPair(‘client_msg_id’, AClientMsgId); JsonStr := JsonObj.ToString; finally JsonObj.Free; end; // Simple retry with exponential backoff Attempt := 0; BackoffMs := 500; // start 500ms while Attempt < 5 do begin Inc(Attempt); try Code := PostJSON(API_URL, JsonStr, API_KEY, Resp); // Success 2xx if (Code >= 200) and (Code < 300) then begin Result := Resp; // provider response (JSON with message id etc.) Exit; end; // Client errors (4xx) - do not retry except 429 if (Code >= 400) and (Code < 500) and (Code <> 429) then raise ESmsError.CreateFmt(‘Permanent error %d: %s’, [Code, Resp]); // Otherwise transient - retry except on E: ESmsError do raise; on E: Exception do begin // treat as transient network error end; end; // Backoff with jitter Sleep(BackoffMs + Random(BackoffMs)); BackoffMs := BackoffMs * 2; end; raise ESmsError.Create(‘Failed to send SMS after retries’); end;
Notes on adapting the sample
- Replace Indy with TNetHTTPClient or other HTTP libraries if preferred.
- Parse provider JSON responses to extract provider message IDs and statuses.
- Implement proper phone number normalization (use libphonenumber bindings or server-side validation).
- Move API_KEY to secure storage and load at runtime.
Conclusion
A robust Delphi SMS client requires secure credential handling, phone number normalization, rate limiting, clear retry logic, and webhook-driven delivery receipt processing. The sample code gives a starting point for sending messages via an HTTP JSON API; extend it with validation, persistent queues, and observability for production use.
Leave a Reply