awaisjameel/texto
Composer 安装命令:
composer require awaisjameel/texto
包简介
A Laravel package to handle messaging (SMS, MMS, WhatsApp) using Twilio, Telnyx, or Meta's Cloud API
README 文档
README
** Unified, extensible Laravel gateway for sending & receiving SMS/MMS over Twilio & Telnyx, plus WhatsApp through Meta's Cloud API. Batteries included: queueing, retries, events, webhooks, polling, typed value objects.**
Texto provides a unified, extensible Laravel package for carrier-grade SMS/MMS and WhatsApp messaging. Built for Laravel 10–13 (PHP 8.1+), it abstracts provider complexities (Twilio, Telnyx, Meta's WhatsApp Cloud API) through consistent contracts and value objects, enabling seamless integration with enterprise messaging workflows.
Key Features:
- Unified API: Single interface for sending SMS/MMS across multiple providers
- Message Persistence: Automatic storage of sent and received messages with full metadata
- Status Tracking: Real-time delivery status updates via webhooks and fallback polling
- Event-Driven: Rich event system for analytics, notifications, and custom automation
- Advanced Twilio Support: Conversations API with auto-provisioned content templates
- Reliability: Exponential backoff retry, queue-based async processing, and graceful degradation
- Security: Webhook signature validation, rate limiting, and shared secret protection
- Extensibility: Plugin architecture for adding new messaging providers
Table of Contents
- Motivation & Philosophy
- Feature Overview
- Quick Start
- Installation
- Configuration (
config/texto.php) - Usage Examples
- Queueing & Async Flow
- Events & Observability
- Data Model & Persistence
- Webhooks (Inbound + Status)
- Security (Signatures, Secrets, Rate Limiting)
- Status Polling (Adaptive Fallback)
- Retry & Backoff Strategy
- Twilio Conversations & Content Templates
- Overriding a Built‑in Driver
- API Reference (Value Objects, Enums, Events, Interfaces)
- Console Commands
- Testing, Fakes & Local Development
- Architecture Overview
- Troubleshooting & FAQ
- Performance Considerations & Best Practices
- Roadmap
- Contributing
- Security Policy
- Migration Guide
- License & Credits
Provider deep dives: Twilio API reference · Telnyx guide · WhatsApp Cloud API guide
1. Motivation & Philosophy
Building messaging features in Laravel applications often involves wrestling with provider-specific APIs that have inconsistent interfaces, error handling, and webhook formats. Without proper abstraction, code becomes littered with conditional logic that breaks when switching providers or adding new ones.
Texto solves this by providing a clean, consistent interface that:
- Eliminates Provider Lock-in: Switch between Twilio, Telnyx, or custom providers with minimal code changes
- Ensures Type Safety: Strongly typed enums and value objects prevent common mistakes
- Promotes Clean Architecture: Clear separation between sending, persistence, and status tracking
- Enables Observability: Comprehensive events and logging for monitoring and debugging
- Handles Edge Cases: Built-in retry logic, queueing, and fallback polling for reliability
- Prioritizes Security: Webhook validation, rate limiting, and shared secret protection
The philosophy is simple: messaging should be a first-class citizen in your Laravel app, not an afterthought that requires constant maintenance.
2. Feature Overview
Core Messaging
- SMS & MMS Support: Send text messages and media attachments through Twilio and Telnyx, plus WhatsApp text/media/template messages via Meta's Cloud API
- Unified API: Single
Texto::send()method works across all providers - Phone Number Validation: Automatic E.164 formatting and validation using libphonenumber
- Media Handling: Support for multiple media URLs per message
Reliability & Performance
- Queue Integration: Async message sending with Laravel queues for high-throughput applications
- Retry Logic: Exponential backoff for transient API failures
- Status Polling: Fallback polling when webhooks are delayed or unavailable
- Rate Limiting: Built-in protection against webhook abuse
Advanced Twilio Features
- Conversations API: Rich conversation management with participant tracking
- Content Templates: Auto-provisioning and reuse of SMS/MMS templates
- Template Variables: Dynamic content insertion for personalized messaging
Observability & Events
- Event System: Four key events (
MessageSent,MessageReceived,MessageFailed,MessageStatusUpdated) - Structured Logging: Comprehensive logging for debugging and monitoring
- Metadata Capture: Rich metadata storage including costs, segments, and custom data
Security & Compliance
- Webhook Validation: Cryptographic signature verification for Twilio (HMAC‑SHA1), Telnyx (Ed25519), and WhatsApp (HMAC‑SHA256)
- Rate Limiting: Configurable per-minute limits on webhook endpoints
- Data Persistence: Optional message storage with configurable retention
Developer Experience
- Type Safety: Strongly typed enums and value objects
- Extensible Architecture: Plugin system for custom providers
- Testing Support: Fake drivers and webhook validation skipping for tests
- Laravel Integration: Service provider auto-discovery and facade registration
3. Quick Start
composer require awaisjameel/texto php artisan texto:install # publishes config + migration & runs migrate php artisan texto:test-send +15551234567 "Hello from Texto"
use Texto; // facade alias configured automatically Texto::send('+15551234567', 'Hello world');
4. Installation
Prerequisites
Before installing Texto, ensure your Laravel application meets these requirements:
- Laravel: 10.x, 11.x, 12.x, or 13.x
- PHP: 8.1 or higher (8.2+ recommended)
- Database: MySQL, PostgreSQL, SQLite, or SQL Server
- Queue System: Any Laravel-supported queue driver (Database recommended for production)
- PHP Extensions:
ext-sodiumfor Telnyx signature verification
Quick Installation
The fastest way to get started:
composer require awaisjameel/texto php artisan texto:install
This command will:
- Publish the configuration file to
config/texto.php - Publish and run the database migration
- Register the service provider and facade
Manual Installation
For more control over the installation process:
# 1. Install the package composer require awaisjameel/texto # 2. Publish configuration (optional - auto-published by texto:install) php artisan vendor:publish --tag=texto-config # 3. Publish migration (optional - auto-published by texto:install) php artisan vendor:publish --tag=texto-migrations # 4. Run migrations php artisan migrate
Provider Setup
If you're not using package auto-discovery, add the service provider to config/app.php:
'providers' => [ // ... other providers Awaisjameel\Texto\TextoServiceProvider::class, ], 'aliases' => [ // ... other aliases 'Texto' => Awaisjameel\Texto\Facades\Texto::class, ],
Verification
After installation, verify everything is working:
php artisan texto:test-send +15551234567 "Hello from Texto!"
This will send a test message using your configured provider and settings.
Environment Variables
# Core TEXTO_DRIVER=twilio # twilio | telnyx | whatsapp TEXTO_STORE_MESSAGES=true # disable to skip DB persistence TEXTO_QUEUE=false # true => SendMessageJob async TEXTO_RETRY_ATTEMPTS=3 TEXTO_RETRY_BACKOFF_START=200 # ms TEXTO_WEBHOOK_SECRET= # optional shared secret header TEXTO_WEBHOOK_RATE_LIMIT=300 # webhook requests per minute per endpoint TEXTO_DEFAULT_REGION=US # for parsing non-E.164 input # Status polling (optional) TEXTO_STATUS_POLL_ENABLED=false TEXTO_STATUS_POLL_MIN_AGE=60 TEXTO_STATUS_POLL_MAX_ATTEMPTS=5 TEXTO_STATUS_POLL_QUEUED_MAX_ATTEMPTS=2 TEXTO_STATUS_POLL_BACKOFF=300 TEXTO_STATUS_POLL_BATCH=100 # Twilio TWILIO_ACCOUNT_SID=... TWILIO_AUTH_TOKEN=... TWILIO_FROM_NUMBER=+15550001111 TWILIO_USE_CONVERSATIONS=true # opt in to the classic Messages API by setting false (default: Conversations API) TWILIO_SMS_TEMPLATE_FRIENDLY_NAME=texto_sms_template TWILIO_MMS_TEMPLATE_FRIENDLY_NAME=texto_mms_template TWILIO_SMS_TEMPLATE_SID= # optional HX... SID; skips template auto-discovery TWILIO_MMS_TEMPLATE_SID= # optional HX... SID; skips template auto-discovery TWILIO_CONVERSATION_PREFIX=Texto TWILIO_CONVERSATION_WEBHOOK_URL= # optional override TWILIO_CONVERSATION_CACHE_TTL=604800 # seconds a conversation SID is reused per (from, to); 0 disables TWILIO_HTTP_TIMEOUT=30 # seconds for outbound API calls # Telnyx TELNYX_API_KEY=... TELNYX_MESSAGING_PROFILE_ID=... TELNYX_FROM_NUMBER=+15550002222 TELNYX_WEBHOOK_SECRET=base64-encoded-public-key TELNYX_WEBHOOK_TOLERANCE=300 # max webhook timestamp age in seconds TELNYX_HTTP_TIMEOUT=30 # seconds for outbound API calls # WhatsApp Cloud API WHATSAPP_ACCESS_TOKEN=... WHATSAPP_PHONE_NUMBER_ID=... WHATSAPP_APP_SECRET=... WHATSAPP_VERIFY_TOKEN=... WHATSAPP_FROM_NUMBER=+15550003333 # local sender record only WHATSAPP_HTTP_TIMEOUT=30 # seconds for outbound API calls
5. Configuration (config/texto.php)
After installation, you'll find the configuration file at config/texto.php. Here's a comprehensive guide to all available options:
Core Settings
| Key | Default | Description |
|---|---|---|
driver |
'twilio' |
Active messaging provider ('twilio', 'telnyx', or 'whatsapp') |
store_messages |
true |
Whether to persist messages in the database |
queue |
false |
Enable async message sending via Laravel queues |
validation.region |
'US' |
Default region for parsing non‑E.164 phone numbers (TEXTO_DEFAULT_REGION) |
Retry Configuration
'retry' => [ 'max_attempts' => env('TEXTO_RETRY_ATTEMPTS', 3), 'backoff_start_ms' => env('TEXTO_RETRY_BACKOFF_START', 200), ],
Controls exponential backoff retry behavior for failed API calls:
max_attempts: Maximum number of retry attempts (default: 3)backoff_start_ms: Initial delay in milliseconds (doubles each retry)
Webhook Security
'webhook' => [ 'secret' => env('TEXTO_WEBHOOK_SECRET'), 'rate_limit' => env('TEXTO_WEBHOOK_RATE_LIMIT', 300), ],
secret: Optional shared secret for webhook authenticationrate_limit: Maximum webhook requests per minute, per webhook endpoint (default: 300)
Status Polling (Fallback)
'status_polling' => [ 'enabled' => env('TEXTO_STATUS_POLL_ENABLED', false), 'min_age_seconds' => env('TEXTO_STATUS_POLL_MIN_AGE', 60), 'max_attempts' => env('TEXTO_STATUS_POLL_MAX_ATTEMPTS', 5), 'queued_max_attempts' => env('TEXTO_STATUS_POLL_QUEUED_MAX_ATTEMPTS', 2), 'backoff_seconds' => env('TEXTO_STATUS_POLL_BACKOFF', 300), 'batch_limit' => env('TEXTO_STATUS_POLL_BATCH', 100), ],
Configures fallback polling for messages stuck in transient states:
enabled: Enable/disable polling (default: false)min_age_seconds: Minimum age before polling startsmax_attempts: Maximum polling attempts per messagebackoff_seconds: Delay between polling attempts
Twilio Configuration
'twilio' => [ 'account_sid' => env('TWILIO_ACCOUNT_SID'), 'auth_token' => env('TWILIO_AUTH_TOKEN'), 'from_number' => env('TWILIO_FROM_NUMBER'), 'use_conversations' => env('TWILIO_USE_CONVERSATIONS', true), 'timeout' => env('TWILIO_HTTP_TIMEOUT', 30), 'sms_template_friendly_name' => env('TWILIO_SMS_TEMPLATE_FRIENDLY_NAME', 'texto_sms_template'), 'mms_template_friendly_name' => env('TWILIO_MMS_TEMPLATE_FRIENDLY_NAME', 'texto_mms_template'), // Optional explicit template SIDs (skip auto discovery/creation if provided) 'sms_template_sid' => env('TWILIO_SMS_TEMPLATE_SID'), 'mms_template_sid' => env('TWILIO_MMS_TEMPLATE_SID'), 'conversation_prefix' => env('TWILIO_CONVERSATION_PREFIX', 'Texto'), 'conversation_webhook_url' => env('TWILIO_CONVERSATION_WEBHOOK_URL'), 'conversation_cache_ttl' => env('TWILIO_CONVERSATION_CACHE_TTL', 604800), ],
Twilio-specific settings for both classic and Conversations API modes (the base_urls array pinning the Messaging, Conversations, and Content API hosts is also part of this block).
Telnyx Configuration
'telnyx' => [ 'base_url' => env('TELNYX_BASE_URL', 'https://api.telnyx.com/v2/'), 'api_key' => env('TELNYX_API_KEY'), 'messaging_profile_id' => env('TELNYX_MESSAGING_PROFILE_ID'), 'from_number' => env('TELNYX_FROM_NUMBER'), 'webhook_secret' => env('TELNYX_WEBHOOK_SECRET'), 'webhook_tolerance_seconds' => env('TELNYX_WEBHOOK_TOLERANCE', 300), 'timeout' => env('TELNYX_HTTP_TIMEOUT', 30), ],
Telnyx API credentials, messaging profile configuration, the base64-encoded Ed25519 public key used to verify webhook signatures, the maximum accepted webhook timestamp age, and a transport timeout (seconds) for outbound REST calls.
WhatsApp Configuration
Configure access_token, phone_number_id, app_secret, and verify_token in the whatsapp block. WhatsApp uses Meta's signed webhook at /texto/webhook/whatsapp and does not support status polling. See the complete WhatsApp Cloud API guide for setup, templates, media, and webhook configuration.
Testing Configuration
'testing' => [ 'skip_webhook_validation' => env('TEXTO_TESTING_SKIP_WEBHOOK_VALIDATION', false), ],
Settings for testing environments to skip webhook signature validation.
6. Usage Examples
Basic SMS Sending
Send a simple text message:
use Texto; $result = Texto::send('+15551234567', 'Hello from Texto!'); // Returns SentMessageResult with status, provider ID, etc. echo $result->status->value; // 'sent' echo $result->providerMessageId; // 'SM1234567890abcdef'
MMS with Media Attachments
Send messages with images, videos, or other media:
$result = Texto::send('+15551234567', 'Check out this photo!', [ 'media_urls' => [ 'https://example.com/image.jpg', 'https://example.com/video.mp4' ] ]);
Per-Message Driver Override
Temporarily use a different provider for specific messages:
// Send via Telnyx instead of default Twilio $result = Texto::send('+15551234567', 'Via Telnyx', [ 'driver' => 'telnyx' ]); // Send via Meta's WhatsApp Cloud API (free-form messages require an open 24-hour service window) $result = Texto::send('+15551234567', 'Via WhatsApp', [ 'driver' => 'whatsapp', ]);
Custom Sender Number and Metadata
Use different sender numbers and attach custom metadata:
$result = Texto::send('+15551234567', 'Welcome to our service!', [ 'from' => '+15550009999', // Different sender number 'metadata' => [ 'campaign' => 'welcome_series', 'user_id' => 12345, 'priority' => 'high' ] ]);
Asynchronous Queue Processing
For high-throughput applications, enable queuing:
// In .env TEXTO_QUEUE=true // In code $result = Texto::send('+15551234567', 'Queued message'); echo $result->status->value; // 'queued' // Start a queue worker php artisan queue:work
Controller Response
Return messages directly from controllers (auto-converts to JSON):
class NotificationController extends Controller { public function sendAlert(Request $request) { $result = Texto::send( $request->phone, 'Alert: ' . $request->message ); // Automatically returns JSON response return $result; } }
Event-Driven Processing
Listen to messaging events for analytics and automation:
// In EventServiceProvider protected $listen = [ \Awaisjameel\Texto\Events\MessageSent::class => [ \App\Listeners\LogMessageSent::class, ], \Awaisjameel\Texto\Events\MessageStatusUpdated::class => [ \App\Listeners\TrackDeliveryStatus::class, ], ]; // Listener example class TrackDeliveryStatus { public function handle(MessageStatusUpdated $event) { $result = $event->result; // Log delivery metrics Log::info('Message delivered', [ 'provider_id' => $result->providerMessageId, 'delivered_at' => now(), ]); } }
Bulk Messaging
Send multiple messages efficiently:
$recipients = ['+15551234567', '+15559876543', '+15551111111']; $messages = []; foreach ($recipients as $phone) { $messages[] = Texto::send($phone, 'Bulk notification'); } // Process results $successful = collect($messages)->where('status.value', 'sent')->count();
International Number Handling
Texto automatically handles international formatting:
// All of these work automatically $phones = [ '+1-555-123-4567', // US format '555.123.4567', // Local format (uses config region) '+44 20 7123 4567', // UK format '0912345678', // National format (parsed with config region) ]; foreach ($phones as $phone) { Texto::send($phone, 'International hello!'); }
7. Queueing & Async Flow
- In queue mode,
Texto::send()stores a queued row (statusqueued). - Dispatches
SendMessageJobwith deterministic primary key. - Job invokes
Texto::send(... ['queued_job'=>true,'queued_message_id'=>X])to perform real API send. - Repository upgrades the exact queued record (no racey pattern matching).
- Status webhooks or polling complete remaining transitions.
Benefits: immediate API responses, backpressure via Laravel queue, deterministic DB state.
Note: The queued job now includes a snapshot of the active driver configuration (API keys, profile IDs, etc.) so workers and scheduled pollers have the same credentials that were present when the message was enqueued. Ensure your queue transport (e.g., database table, Redis) is appropriately protected since provider secrets travel with the job payload.
8. Events & Observability
| Event | Fired When | Payload |
|---|---|---|
MessageSent |
Successful provider send | SentMessageResult |
MessageFailed |
Send attempt threwTextoSendFailedException |
SentMessageResult, error message |
MessageReceived |
Inbound webhook parsed | WebhookProcessingResult |
MessageStatusUpdated |
Stored message status advanced (webhook) | WebhookProcessingResult |
Subscribe in EventServiceProvider or use listeners/jobs for analytics, billing, triggers.
Structured logging is emitted at info / debug levels for sends, polling promotions, template initialization, and failures.
9. Data Model & Persistence
Table: texto_messages
| Column | Notes |
|---|---|
| direction | sent / received |
| driver | twilio / telnyx / whatsapp |
| from_number / to_number | E.164 formatted |
| body | Nullable for pure media inbound |
| media_urls | JSON array |
| status | Normalized enum (queued, sending, sent, delivered, read, received, failed, undelivered, ambiguous) |
| provider_message_id | SID / Telnyx ID (nullable until known) |
| error_code | Provider error (if any) |
| segments_count | (Telnyx) part count |
| cost_estimate | (Telnyx) estimated cost |
| metadata | Arbitrary JSON (includes polling counters, conversation info) |
| sent_at / received_at / status_updated_at | Timestamps |
Ambiguous terminal state occurs when polling exhausts attempts without a provider id or final disposition.
10. Webhooks
Auto‑registered routes:
| Provider | Endpoint | Methods |
|---|---|---|
| Twilio | /texto/webhook/twilio |
POST (inbound + status) |
| Telnyx | /texto/webhook/telnyx |
POST (inbound + status) |
/texto/webhook/whatsapp |
GET (Meta subscription verify handshake) + POST (inbound + status, batched) |
Each provider publishes inbound and status callbacks to a single endpoint. Texto inspects each payload to determine whether it is an inbound message or a delivery status update, ensuring identical processing across providers. WhatsApp webhooks arrive batched; every entry in the batch is processed individually.
Twilio Conversations events authored by your own configured from_number (the echo of a message Texto itself sent) are recognized as authentic and intentionally ignored, so outbound messages are never re‑recorded as inbound.
Each request passes through RateLimitTextoWebhook – a per‑minute throttle (webhook.rate_limit). Authenticity is enforced by each provider's cryptographic signature inside the handler (Twilio X-Twilio-Signature, Telnyx Ed25519, Meta X-Hub-Signature-256). The VerifyTextoWebhookSecret middleware (X-Texto-Secret header, TEXTO_WEBHOOK_SECRET) is not attached to these routes — providers cannot send custom headers — it is shipped for guarding your own application‑defined endpoints.
Inbound payloads are normalized into WebhookProcessingResult then persisted via EloquentMessageRepository.
11. Security
| Mechanism | Description |
|---|---|
| Twilio Signature | X-Twilio-Signature validated (HMAC‑SHA1 over the full public URL + sorted POST params). Behind a TLS‑terminating proxy, configure TrustProxies so the public URL is reconstructed. |
| Telnyx Signature | Ed25519 signature validated against the Telnyx public webhook key (ext-sodium), with timestamp tolerance (TELNYX_WEBHOOK_TOLERANCE). |
| WhatsApp Signature | Meta's X-Hub-Signature-256 (HMAC‑SHA256 of the raw body with WHATSAPP_APP_SECRET) validated; GET verify handshake uses WHATSAPP_VERIFY_TOKEN. |
| Shared Secret Header | VerifyTextoWebhookSecret middleware checks X-Texto-Secret against TEXTO_WEBHOOK_SECRET — for your own endpoints, not the packaged provider routes. |
| Rate Limiting | RateLimitTextoWebhook throttles every packaged webhook endpoint per minute. |
| Phone Parsing | All numbers canonicalized using libphonenumber. |
Signature validation can be skipped in test environments via TEXTO_TESTING_SKIP_WEBHOOK_VALIDATION=true.
12. Status Polling (Fallback)
Some production networks delay webhooks or they can be transiently disabled. Polling covers that gap.
Enable via TEXTO_STATUS_POLL_ENABLED=true. The service provider auto‑schedules StatusPollJob each minute. Logic:
- Select messages in transient states (
queued|sending|sent) older thanmin_age_seconds. - Skip if attempts exceed caps (
max_attempts, orqueued_max_attemptsfor still‑queued w/out provider id). - Enforce backoff between polls via
last_poll_atmetadata. - Promote forward‑only (e.g., queued -> sent) while avoiding regressions.
- Mark terminal on delivered/failed/undelivered. Mark
ambiguouswhen provider id missing after exhaustion.
Metadata counters (poll_attempts, last_poll_at, flags) are merged into metadata JSON for auditability.
13. Retry & Backoff
Retry::exponential() wraps critical provider API calls (send operations). Configured by retry.max_attempts & retry.backoff_start_ms. Delay doubles each attempt until max attempts reached. Exceptions escalate as TextoSendFailedException leading to MessageFailed event emission and (optionally) DB record with status failed.
14. Twilio Conversations & Content Templates
The Conversations API is the default. You opt in to the classic Messages API (one call per SMS, outbound-only) by setting TWILIO_USE_CONVERSATIONS=false. In Conversations mode, Texto:
- Lazily initializes Conversations sub‑client.
- Ensures (or creates) SMS / MMS Content Templates (friendly names configurable).
- Creates (or reuses) a Conversation per send (deduplicates participant collisions & reuses existing).
- Optionally attaches per‑conversation webhook (config
conversation_webhook_urlor metadata override). - Sends message using template variables (splitting long body into up to 5 × 100‑char chunks). Falls back to body variant if template fails.
Captured metadata includes: conversation_sid, conversation_reused, optional conversation_webhook_sid.
Successful sends cache their conversation SID per (from, to) pair (default 7 days, TWILIO_CONVERSATION_CACHE_TTL seconds, 0 disables), so repeat sends to the same recipient skip the conversation setup calls and only POST the message. If a cached conversation was closed or deleted in the meantime, the full setup flow runs once automatically. A conversation created for a send that ultimately fails is deleted again (best effort) so failed sends leave no orphans.
On the classic Messages path, metadata['webhook_url'] is passed to Twilio as the per‑message StatusCallback (mirroring the Telnyx driver), so delivery receipts reach /texto/webhook/twilio without configuring callbacks on the number or messaging service.
Webhook signature note: Twilio signs the public URL of your webhook. If the app runs behind a TLS‑terminating proxy or load balancer, configure Laravel's
TrustProxiesmiddleware sofullUrl()reconstructs the publichttps://URL — otherwise every signature validation fails.
Per‑Instance Credentials (no container singletons)
The package no longer binds the low‑level provider API adapters (TwilioMessagingApiInterface, TelnyxMessagingApiInterface, …) into the container. Adapters capture
credentials at construction, so a boot‑time singleton would silently keep using stale (or another tenant's) credentials under per‑send driver_config overrides. Every
sender now constructs its adapters from its own configuration, and each adapter authenticates its HTTP calls with its own credentials. Tests and custom integrations
inject fake adapters through the sender constructors, e.g. new TwilioSender($config, $fakeMessagingApi).
The HTTP macro Http::twilio() is still credential‑aware; it omits Basic Auth when credentials are missing so generic tests can stub endpoints without failures.
Content Template Creation Robustness
Template creation logic now tolerates varied (mock) response shapes and will parse a sid from either a direct field or nested content record arrays.
No behavioral change is required for production usage; failures still fall back to body‑only send paths.
15. Overriding a Built‑in Driver
extend() swaps the sender used for an existing built‑in driver (twilio, telnyx, or whatsapp). The name
must match a recognized Driver enum value — extending an unknown name throws a
TextoException. To add a genuinely new provider, add a Driver enum case (see below), not extend().
use Awaisjameel\Texto\Contracts\DriverManagerInterface; use Awaisjameel\Texto\Contracts\MessageSenderInterface; use Awaisjameel\Texto\ValueObjects\{PhoneNumber, SentMessageResult}; use Awaisjameel\Texto\Enums\{Driver, Direction, MessageStatus}; // Override the sender resolved for the built-in 'twilio' driver. app(DriverManagerInterface::class)->extend('twilio', function () { return new class implements MessageSenderInterface { public function send(PhoneNumber $to, string $body, ?PhoneNumber $from = null, array $mediaUrls = [], array $metadata = []): SentMessageResult { // ...call provider API... return new SentMessageResult( Driver::Twilio, Direction::Sent, $to, $from, $body, $mediaUrls, $metadata, MessageStatus::Sent, 'custom-123' ); } }; });
Sender requirements:
- Implement
MessageSenderInterface::send()returningSentMessageResult. - Optionally implement
PollableMessageSenderInterface::fetchStatus()for polling compatibility. - Throw
TextoSendFailedExceptionfor terminal send failures.
Adding a brand‑new provider: introduce a new case in the Driver enum, map its raw statuses in
StatusMapper, and register the sender. Each extend() name is bound to a Driver enum case, so a
new provider cannot be resolved until its enum case exists. Contributing it back via PR is encouraged.
16. API Reference
Texto Facade
The main entry point for all messaging operations.
Texto::send(string $to, string $body, array $options = []): SentMessageResult
Send an SMS or MMS message.
Parameters:
$to(string): Recipient phone number (E.164 format or local format)$body(string): Message text content$options(array): Optional configuration
Options:
media_urls(array): Array of media URLs for MMSfrom(string): Override sender numberdriver(string): Override provider ('twilio', 'telnyx', or 'whatsapp')metadata(array): Custom metadata to store with messagedriver_config(array): Optional provider configuration snapshot (API keys, messaging profile IDs, etc.) that temporarily overridesconfig('texto.{driver}')for this send; primarily used by queued jobs or multi-tenant flows.
Note: When supplying
driver_config, remember that any secrets included will travel with the queued job payload and logs you emit. Use encrypted queues or other safeguards appropriate for your environment.
Returns: SentMessageResult object
Example:
$result = Texto::send('+15551234567', 'Hello!', [ 'media_urls' => ['https://example.com/image.jpg'], 'metadata' => ['campaign' => 'welcome'] ]);
Value Objects
PhoneNumber
Represents a validated, E.164 formatted phone number.
class PhoneNumber { public readonly string $e164; public static function fromString(string $raw, ?string $region = null): self }
Methods:
fromString(string $raw, ?string $region = null): Parse and validate phone number__toString(): Returns E.164 formatted number
SentMessageResult
Immutable result object returned after sending a message.
final class SentMessageResult implements Responsable, JsonSerializable { public readonly Driver $driver; public readonly Direction $direction; public readonly PhoneNumber $to; public readonly ?PhoneNumber $from; public readonly string $body; public readonly array $mediaUrls; public readonly array $metadata; public readonly MessageStatus $status; public readonly ?string $providerMessageId; public readonly ?string $errorCode; public function toArray(): array public function jsonSerialize(): array public function toResponse($request): JsonResponse }
WebhookProcessingResult
Result object for webhook processing.
final class WebhookProcessingResult { public readonly Driver $driver; public readonly Direction $direction; public readonly ?PhoneNumber $from; public readonly ?PhoneNumber $to; public readonly ?string $body; public readonly array $mediaUrls; public readonly array $metadata; public readonly ?string $providerMessageId; public readonly ?MessageStatus $status; public static function inbound(Driver $driver, PhoneNumber $from, PhoneNumber $to, ?string $body, array $media, array $metadata, ?string $providerMessageId = null): self public static function status(Driver $driver, ?string $providerMessageId, MessageStatus $status, array $metadata = []): self }
Enums
MessageStatus
Normalized message status values.
enum MessageStatus: string { case Queued = 'queued'; // Message queued for sending case Sending = 'sending'; // Message being sent case Sent = 'sent'; // Message sent successfully case Delivered = 'delivered'; // Message delivered to recipient case Read = 'read'; // Read by recipient (WhatsApp; terminal) case Received = 'received'; // Inbound message received case Failed = 'failed'; // Send failed permanently case Undelivered = 'undelivered'; // Message undelivered case Ambiguous = 'ambiguous'; // Status unknown after polling }
Driver
Available messaging providers.
enum Driver: string { case Twilio = 'twilio'; case Telnyx = 'telnyx'; case Whatsapp = 'whatsapp'; }
Direction
Message direction.
enum Direction: string { case Sent = 'sent'; case Received = 'received'; }
Events
MessageSent
Fired when a message is successfully sent.
class MessageSent { public function __construct(public readonly SentMessageResult $result) {} }
MessageReceived
Fired when an inbound message is received via webhook.
class MessageReceived { public function __construct(public readonly WebhookProcessingResult $result) {} }
MessageStatusUpdated
Fired when a stored message's status advances via a provider status webhook.
class MessageStatusUpdated { public function __construct(public readonly WebhookProcessingResult $result) {} }
MessageFailed
Fired when a message send attempt fails.
class MessageFailed { public function __construct( public readonly SentMessageResult $result, public readonly ?string $reason = null ) {} }
Exceptions
TextoException
Base exception for all Texto-related errors.
TextoSendFailedException
Thrown when message sending fails.
TextoWebhookValidationException
Thrown when webhook validation fails (bad signature or malformed payload). It renders as an HTTP
403 response so providers treat the request as permanently unacceptable and stop retrying, rather
than receiving a 500 that triggers retry storms.
Interfaces
MessageSenderInterface
Contract for message sending implementations.
interface MessageSenderInterface { public function send(PhoneNumber $to, string $body, ?PhoneNumber $from = null, array $mediaUrls = [], array $metadata = []): SentMessageResult; }
MessageRepositoryInterface
Contract for message persistence.
interface MessageRepositoryInterface { public function storeSent(SentMessageResult $result): Model; public function storeInbound(WebhookProcessingResult $result): Model; public function storeStatus(WebhookProcessingResult $result): ?Model; public function updatePolledStatus(Message $message, MessageStatus $status, array $extraMetadata = []): Message; public function upgradeQueued(int $id, SentMessageResult $result): ?Model; }
DriverManagerInterface
Contract for driver management.
interface DriverManagerInterface { public function sender(?Driver $driver = null): MessageSenderInterface; public function extend(string $name, callable $factory): void; }
Console Commands
php artisan texto:install
Install and configure Texto.
php artisan texto:test-send {to} {body?} {--driver=}
Send a test message.
Parameters:
to: Recipient phone numberbody: Message body (default: "Test message")--driver: Override provider driver
17. Console Commands
| Command | Description |
|---|---|
texto:install |
Publish config + migration then run migrate. |
texto:test-send {to} {body?} |
Fire a manual test message (optional--driver=). |
18. Testing, Fakes & Local Development
- Uses Pest & Orchestra Testbench for package isolation.
- Static analysis via PHPStan (
composer analyse). - Code style via Pint (
composer format). - Swap drivers with a fake:
app(\Awaisjameel\Texto\Contracts\DriverManagerInterface::class) ->extend('twilio', fn () => new \Awaisjameel\Texto\Drivers\FakeSender());
- Skip webhook signature validation during tests: set
TEXTO_TESTING_SKIP_WEBHOOK_VALIDATION=true.
Run full suite:
composer test
19. Architecture Overview
| Layer | Responsibility |
|---|---|
Texto facade/root |
Orchestrates send workflow, queue placeholder creation, events. |
DriverManager |
Resolves concrete sender implementation (built‑ins + extensions). |
Drivers (TwilioSender, TelnyxSender, WhatsappSender) |
Provider API invocation + provider‑specific metadata enrichment. Each constructs its own API adapters from its config. |
StatusMapper |
Converts raw provider statuses / events to internal enum. |
EloquentMessageRepository |
Persistence & deterministic queued upgrade + polling updates. |
Jobs (SendMessageJob, StatusPollJob) |
Async send & periodic status reconciliation. |
| Webhook Handlers | Parse & validate inbound/status payloads per provider. |
Support Utilities (Retry, PollingParameterResolver) |
Cross‑cutting helpers. |
| Value Objects / Enums | Strongly typed domain primitives. |
Design goals: minimal public API surface (Texto::send), encapsulated provider variance, explicit lifecycle events, observability via logs + metadata.
20. Troubleshooting & FAQ
Common Issues
Q: Messages stuck in queued status
A: This usually indicates queue processing issues.
- Verify
TEXTO_QUEUE=truein your environment - Ensure a queue worker is running:
php artisan queue:work - Check queue connection configuration
- Review Laravel logs for job processing errors
- Enable status polling as fallback:
TEXTO_STATUS_POLL_ENABLED=true
Q: Webhook signature validation fails (401 errors) A: Signature validation ensures webhook authenticity.
- For Twilio: Verify
TWILIO_AUTH_TOKENmatches your Twilio console - Ensure webhook URLs in provider console exactly match your routes (including protocol; query strings are supported)
- Behind a TLS‑terminating proxy or load balancer, configure Laravel's
TrustProxiesmiddleware so the publichttps://URL is reconstructed for signature checks - For local development, use ngrok or similar tunneling service
Q: Twilio Conversations template creation warnings A: Template auto-provisioning may fail due to permissions.
- This is non-fatal; Texto falls back to direct message sending
- Check Twilio account has Content API permissions
- Verify
TWILIO_ACCOUNT_SIDandTWILIO_AUTH_TOKENare correct - Template creation warnings don't prevent message sending
Q: Telnyx cost/segment data missing A: Cost and segment information is only provided in specific response scenarios.
- Ensure your Telnyx API key has messaging permissions
- Cost data appears only when Telnyx includes it in API responses
- Segment counts depend on message content and provider logic
Q: Messages failing with provider errors A: Check provider account status and configuration.
- Verify API credentials are correct and active
- Ensure sender numbers are verified/purchased in provider console
- Check provider account has sufficient balance/credits
- Review message content for prohibited terms
Q: High memory usage with large message volumes A: Optimize for high-throughput scenarios.
- Enable queuing:
TEXTO_QUEUE=true - Use database queue driver for reliability
- Configure appropriate queue worker settings
- Monitor queue depth and processing rates
Status Definitions
Q: What does ambiguous status mean?
A: Messages reach ambiguous status when polling exhausts all attempts without determining final delivery status.
- Occurs when provider ID is missing and polling can't retrieve status
- Investigate upstream provider logs for root cause
- May indicate provider API issues or message filtering
Q: Difference between failed and undelivered?
A: These represent different failure modes:
failed: Immediate sending failure (invalid number, blocked content, etc.)undelivered: Message sent but delivery failed (phone off, full mailbox, etc.)
Configuration Issues
Q: How to disable message persistence?
A: Set TEXTO_STORE_MESSAGES=false in your environment.
- Events will still fire normally
SentMessageResultobjects are still returned- Useful for testing or when external logging is preferred
Q: Phone number validation too strict A: Adjust the default region for number parsing.
- Set
TEXTO_DEFAULT_REGIONto your primary market (e.g., 'GB' for UK) - This affects how local format numbers are interpreted
- E.164 format (+country code) always works regardless of region
Provider-Specific Issues
Q: Twilio rate limiting A: Twilio enforces sending limits based on account type.
- Free accounts: 100 messages/day
- Trial accounts: Limited sending
- Full accounts: Higher limits based on verification level
- Implement queuing and backoff strategies
Q: Telnyx webhook delays A: Telnyx webhooks may have higher latency than Twilio.
- Enable status polling for critical delivery tracking
- Configure appropriate polling intervals
- Monitor webhook delivery logs
Performance Tuning
Q: Optimizing for high volume A: Several configuration options for performance:
- Use Redis/database queues instead of sync processing
- Configure multiple queue workers
- Enable status polling with appropriate batch sizes
- Monitor database indexes on
texto_messagestable - Consider message archiving for old records
Q: Database performance with many messages
A: The texto_messages table can grow quickly.
- Add database indexes on frequently queried columns
- Implement message archiving/cleanup strategies
- Consider partitioning for very high volume
- Monitor query performance and optimize as needed
Development & Testing
Q: Testing without sending real messages A: Use the fake driver for testing:
app(DriverManagerInterface::class)->extend('twilio', fn() => new FakeSender());
- Skip webhook validation in tests:
TEXTO_TESTING_SKIP_WEBHOOK_VALIDATION=true - Use test credentials or mock HTTP responses
Q: Local development with webhooks A: Webhooks require public URLs for provider access.
- Use ngrok, localtunnel, or similar services
- Configure webhook URLs in provider console
- Consider webhook testing tools like webhook.site for debugging
Extending Texto
Q: Adding a new provider (e.g., Vonage)
A: extend() only overrides the senders of built‑in drivers (twilio, telnyx, whatsapp); passing an
unrecognized name (such as 'vonage') throws a TextoException. A genuinely new provider needs its
own Driver enum case so the manager can resolve it:
// 1. Add a case to the Driver enum (e.g. case Vonage = 'vonage';) and map its statuses in StatusMapper. // 2. Register the sender for that driver: app(DriverManagerInterface::class)->extend('vonage', function() { return new class implements MessageSenderInterface { public function send(PhoneNumber $to, string $body, ?PhoneNumber $from = null, array $mediaUrls = [], array $metadata = []): SentMessageResult { // Your implementation } }; });
- Adding the
Driverenum case is required first; without itextend('vonage', ...)is rejected. - Consider contributing back via PR for official support.
- Follow existing driver patterns for consistency.
Q: Custom webhook handling A: Extend webhook handlers for custom logic:
- Create custom handler class implementing
WebhookHandlerInterface - Register in service provider or route configuration
- Handle provider-specific webhook formats
21. Performance Considerations & Best Practices
Database Optimization
For high-volume applications, optimize the texto_messages table:
-- Add performance indexes CREATE INDEX idx_texto_messages_status_created ON texto_messages (status, created_at); CREATE INDEX idx_texto_messages_provider_id ON texto_messages (provider_message_id); CREATE INDEX idx_texto_messages_from_to ON texto_messages (from_number, to_number); -- Consider partitioning for very high volume -- Partition by month for message archiving ALTER TABLE texto_messages PARTITION BY RANGE (YEAR(created_at)) ( PARTITION p2024 VALUES LESS THAN (2025), PARTITION p2025 VALUES LESS THAN (2026) );
Queue Configuration
For reliable message processing at scale:
// config/queue.php 'connections' => [ 'database' => [ 'driver' => 'database', 'table' => 'jobs', 'queue' => 'default', 'retry_after' => 90, // Increase for messaging jobs ], ],
Run multiple workers for high throughput:
# Multiple workers for parallel processing
php artisan queue:work --queue=texto-high,texto-normal --max-jobs=1000 --sleep=3
php artisan queue:work --queue=texto-bulk --max-jobs=500 --sleep=5
Monitoring & Alerting
Implement monitoring for critical messaging operations:
// Monitor queue health $pendingJobs = DB::table('jobs')->where('queue', 'like', 'texto%')->count(); if ($pendingJobs > 1000) { Log::warning('High texto queue backlog', ['count' => $pendingJobs]); } // Monitor failure rates $failureRate = Message::where('status', 'failed') ->where('created_at', '>', now()->subHour()) ->count() / Message::where('created_at', '>', now()->subHour())->count(); if ($failureRate > 0.1) { // 10% failure rate // Alert or take action }
Cost Optimization
Track and optimize messaging costs:
// Analyze costs by provider and campaign $costs = Message::selectRaw(' driver, SUM(cost_estimate) as total_cost, COUNT(*) as message_count, AVG(cost_estimate) as avg_cost ') ->whereNotNull('cost_estimate') ->where('created_at', '>', now()->subMonth()) ->groupBy('driver') ->get(); // Implement cost thresholds if ($costs->sum('total_cost') > 1000) { // Monthly budget // Send alert or implement throttling }
Security Best Practices
Secure your messaging infrastructure:
// Use environment variables for secrets // Never commit API keys to version control // Implement rate limiting per user/phone RateLimiter::for('texto-send', function (Request $request) { return Limit::perMinute(10)->by($request->user()->id); }); // Validate phone numbers strictly $phone = PhoneNumber::fromString($request->phone, 'US'); // Specify region if (!$phone) { throw new InvalidPhoneNumberException(); }
Error Handling & Resilience
Implement comprehensive error handling:
try { $result = Texto::send($phone, $message, $options); } catch (TextoSendFailedException $e) { // Log detailed error Log::error('Message send failed', [ 'phone' => $phone, 'error' => $e->getMessage(), 'driver' => config('texto.driver') ]); // Implement fallback logic if (config('texto.driver') === 'twilio') { // Try Telnyx as fallback $result = Texto::send($phone, $message, ['driver' => 'telnyx'] + $options); } // Notify user or take alternative action }
Testing Strategies
Comprehensive testing approach:
// Unit tests for drivers class TwilioSenderTest extends TestCase { public function test_sends_message_successfully() { // Mock Twilio client $this->mock(TwilioClient::class, function ($mock) { $mock->shouldReceive('messages->create') ->once() ->andReturn((object)['sid' => 'SM123']); }); $result = app(TwilioSender::class)->send( PhoneNumber::fromString('+15551234567'), 'Test message' ); $this->assertEquals(MessageStatus::Sent, $result->status); } } // Integration tests with fake driver class MessagingIntegrationTest extends TestCase { protected function setUp(): void { parent::setUp(); // Use fake driver for integration tests app(DriverManagerInterface::class)->extend('twilio', fn() => new FakeSender()); } }
Scaling Considerations
For enterprise-level messaging:
- Horizontal Scaling: Deploy across multiple servers with shared queue
- Database Sharding: Split message storage across multiple databases
- CDN for Media: Use CDNs for MMS media to reduce bandwidth
- Provider Redundancy: Implement multi-provider failover logic
- Caching: Cache frequently used phone number validations
- Async Processing: Always use queues for production deployments
Compliance & Data Protection
Handle sensitive messaging data appropriately:
// Implement data retention policies Message::where('created_at', '<', now()->subMonths(6)) ->whereIn('status', ['delivered', 'failed']) ->delete(); // Encrypt sensitive metadata $message->metadata = encrypt(json_encode([ 'ssn' => $sensitiveData, // Encrypted storage 'campaign' => 'public_data' ])); // Implement audit logging Log::channel('messaging-audit')->info('Message sent', [ 'id' => $message->id, 'to' => $message->to_number, // Log for compliance 'timestamp' => now(), 'user_id' => auth()->id() ]);
22. Roadmap
Planned Features
- Multi-provider Routing: Intelligent load balancing and failover across providers
- Additional Providers: Official support for MessageBird, Vonage, AWS SNS, and others
- Template Engine: Unified templating system for all providers
- Bulk Operations: Batch sending with progress tracking and error aggregation
- Advanced Analytics: Built-in reporting and analytics dashboard
- Webhook Enhancements: Improved webhook signature verification and replay protection
- Rate Limiting: Provider-aware rate limiting and throttling
- Geographic Routing: Route messages via local providers for cost optimization
Community Contributions
We welcome contributions! Areas of particular interest:
- New provider implementations
- Performance optimizations
- Enhanced testing utilities
- Documentation improvements
- Integration packages for popular frameworks
Version Compatibility
| Texto Version | Laravel Version | PHP Version | Status |
|---|---|---|---|
| 1.x | 10.x - 13.x | 8.1+ | Active |
See the Migration Guide below and CHANGELOG.md for upgrade notes.
23. Contributing
We welcome contributions from the community! Here's how to get involved:
Development Setup
# Fork and clone the repository git clone https://github.com/your-username/texto.git cd texto # Install dependencies composer install # Copy environment file and configure cp .env.example .env # Add your Twilio/Telnyx test credentials # Run tests composer test # Run static analysis composer analyse # Format code composer format
Contribution Guidelines
-
Open an Issue First: For significant changes, open a descriptive issue to discuss the proposed changes.
-
Code Quality: All contributions must pass quality checks:
composer analyse # PHPStan static analysis composer format # Laravel Pint code formatting composer test # Pest test suite
-
Testing: Add tests for new features and bug fixes:
- Unit tests for classes and methods
- Integration tests for full workflows
- Use the
FakeSenderfor testing without external APIs
-
Documentation: Update documentation for user-visible changes:
- README.md for new features and usage examples
- Inline code documentation (PHPDoc)
- CHANGELOG.md for version history
-
Type Safety: Keep new public APIs strongly typed using PHP 8.1+ features (enums, readonly properties, constructor promotion).
Code Style
Follow Laravel's coding standards with Pint configuration:
// Good: Use type hints and return types public function send(PhoneNumber $to, string $body): SentMessageResult // Good: Use enums for fixed values public function __construct(public readonly MessageStatus $status) // Good: Comprehensive PHPDoc /** * Send an SMS/MMS message using the active driver. * * @param string $to E.164 formatted recipient number * @param string $body Message body text * @param array{media_urls?:string[], metadata?:array} $options */
Testing Strategy
// Unit test example test('phone number validation', function () { $phone = PhoneNumber::fromString('+15551234567'); expect($phone->e164)->toBe('+15551234567'); }); // Integration test example test('message sending workflow', function () { // Use fake driver to avoid external calls app(DriverManagerInterface::class)->extend('twilio', fn() => new FakeSender()); $result = Texto::send('+15551234567', 'Test message'); expect($result->status)->toBe(MessageStatus::Sent); expect($result->providerMessageId)->toBeString(); });
Pull Request Process
-
Branch Naming: Use descriptive branch names:
feature/add-vonage-driverfix/webhook-validation-bugdocs/improve-api-reference
-
Commit Messages: Follow conventional commits:
feat: add Vonage driver supportfix: resolve webhook signature validationdocs: update API reference section
-
PR Description: Include:
- Clear description of changes
- Screenshots for UI changes (if applicable)
- Test coverage information
- Breaking changes (if any)
-
Review Process: All PRs require review and must pass CI checks.
Areas for Contribution
High Priority:
- New provider implementations (MessageBird, Vonage, AWS SNS)
- Performance optimizations for high-volume sending
- Enhanced webhook security features
Medium Priority:
- Additional testing utilities and helpers
- Documentation improvements and translations
- Integration packages for popular Laravel packages
Good for Beginners:
- Bug fixes and small improvements
- Additional code examples and tutorials
- Test coverage improvements
Community Support
- Discussions: Use GitHub Discussions for questions and ideas
- Issues: Report bugs and request features via GitHub Issues
Recognition
Contributors are recognized in:
- CHANGELOG.md for significant contributions
- GitHub's contributor insights
- Social media mentions for major features
Thank you for contributing to Texto! 🎉
24. Security Policy
Report vulnerabilities privately via GitHub Security Advisories. Do not disclose publicly until patched. Avoid sharing live credentials or full raw webhook payloads containing PII in issues.
25. Migration Guide
Upgrading Versions
Unreleased (next version)
Breaking: the low-level provider API adapters (TwilioMessagingApiInterface, TelnyxMessagingApiInterface, WhatsappApiInterface, …) are no longer bound in the service container. If you resolved them via app(...), construct them directly with the credentials they should use (e.g. new TelnyxMessagingApi($apiKey)), or inject a custom implementation through the sender constructors (new TwilioSender($config, $fakeMessagingApi)). See CHANGELOG.md for the full list of changes.
From 1.0.x to 1.1.x
No breaking changes. New features include:
- Enhanced status polling with configurable backoff strategies
- Improved error handling and structured logging
- Additional metadata fields for cost tracking
- Better webhook validation and security
Migration Steps:
- Update package:
composer update awaisjameel/texto - Review new configuration options in
config/texto.php - Update environment variables if needed
- Test webhook endpoints with new validation
From 0.x to 1.x
Breaking changes in the 1.0 release:
Configuration Changes:
// Old (0.x) 'driver' => env('TEXTO_DRIVER', 'twilio'), // New (1.x) - same, but additional options available 'driver' => env('TEXTO_DRIVER', 'twilio'), 'store_messages' => env('TEXTO_STORE_MESSAGES', true), 'queue' => env('TEXTO_QUEUE', false),
API Changes:
// Old (0.x) Texto::send('+15551234567', 'Hello'); // New (1.x) - same API, enhanced return type $result = Texto::send('+15551234567', 'Hello'); $result->status; // Now returns MessageStatus enum
Migration Steps:
- Backup your database
- Update to 1.x:
composer update awaisjameel/texto - Run
php artisan texto:installto update config and migrations - Update any code using status strings to use
MessageStatusenums - Test thoroughly in staging environment
Environment Variable Changes
| Old Variable | New Variable | Notes |
|---|---|---|
| - | TEXTO_STORE_MESSAGES |
Control message persistence |
| - | TEXTO_QUEUE |
Enable async processing |
| - | TEXTO_WEBHOOK_SECRET |
Shared secret for webhook auth |
| - | TEXTO_STATUS_POLL_ENABLED |
Enable status polling fallback |
Database Schema Changes
Version 1.x adds new columns to texto_messages:
-- New columns in 1.x ALTER TABLE texto_messages ADD COLUMN segments_count INT NULL; ALTER TABLE texto_messages ADD COLUMN cost_estimate DECIMAL(10,4) NULL; ALTER TABLE texto_messages ADD COLUMN status_updated_at TIMESTAMP NULL;
These are nullable and backward compatible.
Webhook URL Changes
Webhook routes remain the same but include enhanced validation:
/texto/webhook/twilio- Twilio webhooks (inbound + status)/texto/webhook/telnyx- Telnyx webhooks (inbound + status)
Ensure your provider console webhook URLs match exactly.
Testing Changes
Update your tests to use the new FakeSender:
// Old approach // Custom mock setup // New approach app(DriverManagerInterface::class)->extend('twilio', fn() => new FakeSender());
26. License & Credits
License
Released under the MIT License. See LICENSE.md for details.
Credits
Created by: awaisjameel
Inspiration & Thanks:
- Spatie Laravel Package Tools - Package skeleton
- Laravel OSS Ecosystem - Best practices and patterns
- Twilio & Telnyx Developer Communities - API insights
Contributors
We'd like to thank all contributors who have helped make Texto better:
Sponsors
Support Texto's development:
Related Projects
- Laravel Notification Channels - Alternative notification approach
- Twilio PHP SDK - Official Twilio library
- Telnyx Messaging API Reference - REST endpoints used by Texto's Telnyx driver
Made with ❤️ for the Laravel community
Quick Reference Cheat Sheet
// Basic SMS $result = Texto::send('+15551234567', 'Hello World!'); // MMS with media $result = Texto::send('+15551234567', 'Check this out!', [ 'media_urls' => ['https://example.com/image.jpg'] ]); // Override provider per message $result = Texto::send('+15551234567', 'Via Telnyx', [ 'driver' => 'telnyx' ]); // Custom sender and metadata $result = Texto::send('+15551234567', 'Promotional message', [ 'from' => '+15550009999', 'metadata' => ['campaign' => 'spring_sale', 'priority' => 'high'] ]); // Async processing (when TEXTO_QUEUE=true) $result = Texto::send('+15551234567', 'Queued message'); // $result->status === MessageStatus::Queued // Event listeners Event::listen(MessageSent::class, function ($event) { Log::info('Message sent', ['id' => $event->result->providerMessageId]); });
Support & Community
- 📖 Documentation: You're reading it! Check the GitHub repository for the latest updates
- 🐛 Bug Reports: Open an issue on GitHub
- 💡 Feature Requests: Start a discussion on GitHub
- ⭐ Show Support: Star the repo if Texto saves you time and effort!
Built with ❤️ for the Laravel community by awaisjameel
awaisjameel/texto 适用场景与选型建议
awaisjameel/texto 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 741 次下载、GitHub Stars 达 0, 最近一次更新时间为 2025 年 11 月 06 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「sms」 「messaging」 「twilio」 「laravel」 「messages」 「whatsapp」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 awaisjameel/texto 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 awaisjameel/texto 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 awaisjameel/texto 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
SDK for payment gateway PlatbaMobilom.sk for PHP7.0
Componente para integração com serviços de mensageria com APIs externas como Twilio, SendGrid, AWS SES, etc.
PHP SDK for Mobile Message SMS API
Extensible library for building notifications and sending them via different delivery channels.
Azure service bus Transport
统计信息
- 总下载量: 741
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 30
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2025-11-06