PHPackages                             fissible/phone - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. fissible/phone

ActiveLibrary[API Development](/categories/api)

fissible/phone
==============

Laravel package for Twilio-backed business voice and SMS workflows.

v1.0.0-rc.1(today)00MITPHPPHP ^8.2CI passing

Since Jun 20Pushed todayCompare

[ Source](https://github.com/fissible/phone)[ Packagist](https://packagist.org/packages/fissible/phone)[ RSS](/packages/fissible-phone/feed)WikiDiscussions main Synced today

READMEChangelog (1)Dependencies (14)Versions (2)Used By (0)

Fissible Phone
==============

[](#fissible-phone)

Fissible Phone is an open-source Laravel package for building Twilio-backed business phone workflows: SMS, voice webhooks, call routing, voicemail, webhook security, and AI-answering handoff. It is UI-free and extendable through contracts.

Documentation
-------------

[](#documentation)

Full guides live in **[docs/](docs/README.md)** — installation, Twilio setup, configuration, SMS, voice, voicemail, AI handoff, webhook security, console commands, testing, compliance, and a production checklist.

Planning/internal docs: [Scope](docs/SCOPE.md) · [Roadmap](docs/ROADMAP.md) · [V1 Design](docs/V1_DESIGN.md) · [Release Policy](docs/RELEASE.md) · [Changelog](CHANGELOG.md)

Goals
-----

[](#goals)

- Provide Laravel-native Twilio webhook handling for SMS, MMS, voice, call status, recordings, and voicemail events.
- Store message threads, call records, recordings, routing decisions, and webhook receipts in application tables.
- Make outbound SMS and call actions queue-friendly, idempotent, and auditable.
- Expose extension points for contact lookup, CRM logging, business-hour routing, opt-out policy, notifications, and AI handoff.
- Keep the package usable by any Laravel application. Station support should be implemented as an adapter, not baked into this package.

Non-goals
---------

[](#non-goals)

- Replacing Twilio. This package is built around Twilio primitives.
- Shipping a full hosted phone app on day one.
- Assuming Filament, Livewire, Inertia, or any specific admin UI.
- Assuming Station, Fissible CRM, or Fissible AI.
- Becoming a contact-center product like Twilio Flex.

Planned Package Shape
---------------------

[](#planned-package-shape)

The initial package is Laravel-first:

- config, migrations, routes, jobs, events, models, and services
- Twilio request validation and webhook normalization
- TwiML response builders for common routing flows
- outbound messaging services and queue jobs
- extension interfaces for contacts, inbox ownership, routing, and AI sessions

If reusable non-Laravel code becomes substantial, it can later be extracted into a small `fissible/phone-core` PHP package. Starting in Laravel keeps the first version grounded in the workflows that actually matter: webhooks, persistence, queues, events, and application integration.

Current Pre-alpha API
---------------------

[](#current-pre-alpha-api)

The current build includes package config, service-provider bindings, a `Phone`facade, a Twilio provider adapter, and a test fake.

```
use Fissible\Phone\Facades\Phone;

Phone::messages()
    ->to('+16615551212')
    ->body("We're on for this morning.")
    ->contact(type: 'lead', id: 123, name: 'Sam Lead')
    ->allowUnknownRecipient()
    ->send();
```

`send()` persists a `phone_messages` row, runs the guarded send job synchronously, and returns the updated `PhoneMessage` model. Use `queue()` to persist the message and dispatch the send job through Laravel's bus:

```
Phone::messages()
    ->to('+16615551212')
    ->body('Crew is on site.')
    ->allowUnknownRecipient()
    ->queue();
```

For tests:

```
$fake = Phone::fake();

Phone::messages()
    ->to('+16615551212')
    ->body('Crew is on site.')
    ->allowUnknownRecipient()
    ->send();

$fake->messages();
```

Outbound sends are idempotent at the message row level. The send job atomically claims a `queued` row before calling Twilio and exits without sending if the row already has a provider SID or is no longer sendable. Unexpected provider failures are marked `send_unknown` instead of being blindly retried, so an ambiguous timeout cannot double-text a customer.

Twilio sender precedence is:

1. explicit Messaging Service SID
2. configured default Messaging Service SID
3. explicit `from` number
4. configured default `from` number

By default, outbound SMS is blocked unless the recipient already has a `phone_threads` record for the selected local number. This keeps automated sends from accidentally texting an unresolved number. A host app can deliberately opt in per send with `allowUnknownRecipient()` or globally:

```
PHONE_SMS_ALLOW_UNKNOWN_RECIPIENTS=true
```

Existing threads with `opted_out_at` set are always blocked by the default message policy.

Outbound sends can carry a resolved contact reference using `contact()` or `contactIdentity()`. Contact attribution is stored on `phone_messages.metadata.contact`; when a thread exists, it is also stored on `phone_threads.metadata.contact` and mirrored into the thread's `remote_display_name`, `contact_type`, and `contact_id` columns.

When a Twilio status callback reaches `POST /phone/twilio/sms/status`, the package looks up the outbound `phone_messages` row by provider SID and applies a deterministic status progression. Lower-rank callbacks are ignored, terminal states do not regress, carrier failure details are stored on the message, and `Fissible\Phone\Events\MessageDeliveryUpdated` is dispatched only after the message update is persisted.

### Webhook foundation

[](#webhook-foundation)

The package now registers stateless Twilio webhook routes under `PHONE_ROUTE_PREFIX`, which defaults to `/phone`. The routes use only the `phone.twilio` middleware by default, so Twilio POSTs do not pass through Laravel's session or CSRF middleware.

Set `PHONE_WEBHOOK_BASE_URL` in production when the app is behind a TLS terminating proxy:

```
PHONE_WEBHOOK_BASE_URL=https://example.com
TWILIO_VALIDATE_WEBHOOKS=true
```

That value is used verbatim with the incoming request path and query string before Twilio signature validation. This avoids the common proxy failure where Laravel sees an internal `http://` URL but Twilio signed the public `https://`URL.

Initial webhook routes:

- `POST /phone/twilio/sms/inbound`
- `POST /phone/twilio/sms/status`
- `POST /phone/twilio/voice/inbound`
- `POST /phone/twilio/voice/dial-status`
- `POST /phone/twilio/voice/status`
- `POST /phone/twilio/voice/recording`
- `POST /phone/twilio/voice/transcription`
- `POST /phone/twilio/ai/status`

Each request is stored in `phone_webhook_receipts` with the reconstructed public URL, signature result, request hash, provider SID, processing status, redacted headers, and optional payload. Invalid signatures are rejected with `403` after a minimal forensic receipt is written. Exact webhook retries are deduplicated by a request hash.

Inbound SMS now creates durable records:

- `phone_numbers` for local Twilio numbers
- `phone_threads` for each local/remote SMS conversation
- `phone_messages` for inbound SMS/MMS payloads

If an inbound local number is not already configured, the default resolver creates it in the configured default scope:

```
PHONE_DEFAULT_SCOPE_KEY=global
```

Pre-create `phone_numbers` rows when a host app needs tenant-specific scoping. Inbound webhook scope is copied from the matched local number, not from request context. Accepted inbound SMS/MMS webhooks dispatch `Fissible\Phone\Events\InboundMessageReceived` after persistence.

Inbound STOP-style keywords set `phone_threads.opted_out_at`; START-style keywords clear it. The default keyword lists are US SMS-oriented and can be replaced by binding your own `Fissible\Phone\Contracts\OptOutPolicy`.

Host apps can enrich SMS threads by binding `Fissible\Phone\Contracts\ContactResolver`. The resolver receives a lightweight `ContactLookup` and returns a `ContactIdentity`; resolved identities are stored under `phone_threads.metadata.contact`. The package does not create or own contact records.

Inbound voice now creates a `phone_calls` record and returns TwiML from the configured router. The default router uses the matched `phone_numbers` row when it has `routing_mode=forward` and `forward_to` set, otherwise it falls back to `PHONE_FORWARD_TO`:

```
PHONE_FORWARD_TO=+16615559999
```

Forwarded calls include a dial action callback to `/phone/twilio/voice/dial-status`. If no forward destination is configured, the default router returns simple voicemail TwiML with a recording status callback tagged as `purpose=voicemail`. Host apps can replace routing by binding `Fissible\Phone\Contracts\CallRouter`.

Inbound voice contact lookup is deferred. The voice webhook stores the call and queues `Fissible\Phone\Jobs\ResolveInboundCallContact` after the response so a slow CRM lookup cannot delay TwiML. Resolved contacts are stored under `phone_calls.metadata.contact`; resolver failures are captured under `phone_calls.metadata.contact_resolution`.

Business-hours routing is built into the default forward mode. If no weekly hours are configured, numbers are treated as always open. Once weekly hours are configured, calls forward only inside those windows and use `phone.default_voice.after_hours_mode` outside them:

```
'business_hours' => [
    'timezone' => 'America/Los_Angeles',
    'weekly' => [
        'monday' => [['start' => '09:00', 'end' => '17:00']],
        'tuesday' => [['start' => '09:00', 'end' => '17:00']],
        'wednesday' => [['start' => '09:00', 'end' => '17:00']],
        'thursday' => [['start' => '09:00', 'end' => '17:00']],
        'friday' => [['start' => '09:00', 'end' => '17:00']],
    ],
    'holidays' => [
        '2026-12-25',
    ],
],
```

Individual `phone_numbers.business_hours` values override the global business-hours config for that number. Day windows may also be written as strings such as `09:00-17:00`; use `false` or `closed` for a closed day.

When Twilio reaches `POST /phone/twilio/voice/status` or the `` action callback at `POST /phone/twilio/voice/dial-status`, the package updates the matching `phone_calls` row with the same deterministic progression used for SMS: lower-rank callbacks are ignored and terminal call states do not regress. Dial action callbacks return an empty TwiML `` after persistence so Twilio gets a valid voice response.

Recording callbacks create `phone_recordings`. A recording only creates a `phone_voicemails` row when the callback is tagged with `purpose=voicemail`, so future QA/compliance recordings can share the same recording table without being treated as customer voicemails.

Voicemail transcription is opt-in:

```
PHONE_TRANSCRIBE_VOICEMAILS=true
```

When enabled, voicemail TwiML includes a Twilio `transcribeCallback` pointing at `POST /phone/twilio/voice/transcription`. Transcription callbacks create `phone_transcriptions`; completed voicemail transcriptions also update the matching `phone_voicemails.transcription_text`.

### Diagnostics

[](#diagnostics)

Run a local configuration check with:

```
php artisan phone:doctor
```

The command checks Twilio credentials, sender configuration, webhook base URL, stateless webhook middleware, and default voice routing. Add `--live` to make a single Twilio API request and verify that the configured credentials work.

### Activity Logging

[](#activity-logging)

Bind `Fissible\Phone\Contracts\ActivityLogger` to mirror package events into a CRM, audit log, or host app activity stream. The default logger is a no-op.

The package currently logs structured activity entries for inbound SMS and inbound voice after local persistence. Keep custom loggers fast in webhook requests; for slow CRM work, prefer Laravel event listeners or queued jobs using the persisted package events.

### Team Notifications

[](#team-notifications)

Bind `Fissible\Phone\Contracts\TeamNotifier` to send lightweight notifications to a host app, Slack, email, push system, or queue. The default notifier is a no-op. Notifications are UI-free `TeamNotification` value objects containing the persisted package models and provider metadata.

The package currently emits team notifications for:

- inbound SMS (`sms.inbound`)
- missed inbound calls (`voice.missed`)
- new voicemails (`voicemail.received`)

Missed-call notifications are emitted only when a status callback actually moves an inbound call into an unanswered terminal state, so duplicate provider retries do not notify the team twice. Keep custom notifiers fast in webhook requests; if delivery can block, hand the `TeamNotification` to a queued job.

Early Milestones
----------------

[](#early-milestones)

1. Twilio credentials, config, and webhook signature validation.
2. Inbound SMS webhook storage and normalized message events.
3. Outbound SMS service with queued sends and status callbacks.
4. Voice webhook responses for forwarding, business hours, missed-call handling, and voicemail.
5. Call, recording, and voicemail persistence.
6. Contact lookup and activity logging contracts.
7. Optional UI package for shared inbox, call log, voicemail, and settings.
8. AI answering integration using Twilio Conversation Relay or Agent Connect.

License
-------

[](#license)

Fissible Phone is open-source software licensed under the MIT license.

###  Health Score

35

—

LowBetter than 77% of packages

Maintenance100

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity31

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

0d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/ef298fbffac2225e36ab3a6e089af020f76885bc65d5f6afda831c64939e1e4b?d=identicon)[fissible](/maintainers/fissible)

---

Top Contributors

[![fissible](https://avatars.githubusercontent.com/u/1410914?v=4)](https://github.com/fissible "fissible (26 commits)")

---

Tags

laravelphonephpsmstwiliovoicelaravelphonesmstwiliovoicevoicemail

###  Code Quality

TestsPest

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/fissible-phone/health.svg)

```
[![Health](https://phpackages.com/badges/fissible-phone/health.svg)](https://phpackages.com/packages/fissible-phone)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3325.1M337](/packages/psalm-plugin-laravel)[larastan/larastan

Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel

6.4k51.0M7.5k](/packages/larastan-larastan)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k14.1M122](/packages/laravel-pulse)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9732.3M121](/packages/roots-acorn)[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

76518.2M115](/packages/laravel-mcp)[api-platform/laravel

API Platform support for Laravel

59156.3k11](/packages/api-platform-laravel)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
