joepages / laravel-emails
Polymorphic emails package for Laravel - Attach N emails to any model
Installs: 98
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/joepages/laravel-emails
Requires
- php: ^8.2
- illuminate/contracts: ^11.0|^12.0
- illuminate/database: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
Requires (Dev)
- mockery/mockery: ^1.6
- orchestra/testbench: ^9.0|^10.0
- phpunit/phpunit: ^11.0|^12.0
README
Polymorphic email addresses for Laravel. Attach multiple email addresses to any Eloquent model with full CRUD, bulk sync, primary management, verification tracking, and multi-tenancy awareness.
Requirements
- PHP 8.2+
- Laravel 11 or 12
Installation
composer require joepages/laravel-emails
Run the install command to publish the config and migrations:
php artisan emails:install php artisan migrate
The installer auto-detects stancl/tenancy and publishes migrations to database/migrations/tenant/ when present.
Install options
php artisan emails:install --force # Overwrite existing files php artisan emails:install --skip-migrations # Only publish config
Quick Start
1. Add the trait to your model
use Emails\Concerns\HasEmails; class Facility extends Model { use HasEmails; }
2. Add the controller trait
use Emails\Concerns\ManagesEmails; class FacilityController extends BaseApiController { use ManagesEmails; }
3. Register routes
Route::emailRoutes('facilities', FacilityController::class);
This registers the following routes:
| Method | URI | Action |
|---|---|---|
| GET | /facilities/{facility}/emails |
listEmails |
| POST | /facilities/{facility}/emails |
storeEmail |
| PUT | /facilities/{facility}/emails/{email} |
updateEmail |
| DELETE | /facilities/{facility}/emails/{email} |
deleteEmail |
Model Trait API
The HasEmails trait provides three relationships on your model:
$facility->emails; // All emails (MorphMany) $facility->primaryEmail; // Primary email (MorphOne) $facility->emailsOfType('work'); // Filtered by type (MorphMany)
Email Model
Fields
| Field | Type | Description |
|---|---|---|
type |
string | Email type (personal, work, billing, other) |
is_primary |
boolean | Whether this is the primary email |
email |
string | The email address |
is_verified |
boolean | Whether the email has been verified |
verified_at |
datetime|null | When the email was verified |
metadata |
array|null | Custom JSON data |
Scopes
Email::primary()->get(); // Only primary emails Email::ofType('work')->get(); // Filter by type Email::forModel($facility)->get(); // All emails for a specific model Email::verified()->get(); // Only verified emails
Helpers
$email->markAsPrimary(); // Sets as primary, unsets all others for the same parent $email->markAsVerified(); // Sets is_verified=true and verified_at=now $email->domain; // "example.com" (domain part of the address)
Controller Trait
The ManagesEmails trait provides two integration modes:
Standalone CRUD
Use the storeEmail, updateEmail, deleteEmail, and listEmails methods directly via the route macro.
Bulk Sync via BaseApiController
When your controller extends BaseApiController, the attachEmail() method is called automatically during store() and update(). Send an emails array in the request body:
{
"name": "Main Facility",
"emails": [
{
"id": 1,
"email": "updated@example.com"
},
{
"email": "billing@example.com",
"type": "billing",
"is_primary": true
}
]
}
- Records with an
idare updated - Records without an
idare created - Existing records not included in the array are deleted
API Resource
Add emails to your JSON responses:
use Emails\Concerns\WithEmailsResource; class FacilityResource extends JsonResource { use WithEmailsResource; public function toArray($request): array { return [ 'id' => $this->id, 'name' => $this->name, ...$this->emailsResource(), ]; } }
Validation
The EmailRequest form request validates:
| Field | Rules |
|---|---|
email |
required, email, max:255 |
type |
sometimes, string (validated against config when allow_custom_types is false) |
is_primary |
sometimes, boolean |
is_verified |
sometimes, boolean |
verified_at |
nullable, date |
metadata |
nullable, array |
Configuration
// config/emails.php return [ // 'auto' detects stancl/tenancy, 'single' or 'multi' to force 'tenancy_mode' => 'auto', // Allowed email types 'types' => ['personal', 'work', 'billing', 'other'], // Default type when none specified 'default_type' => 'personal', // When false, only types in the 'types' array are accepted 'allow_custom_types' => true, ];
Database Schema
CREATE TABLE emails ( id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, emailable_type VARCHAR(255) NOT NULL, emailable_id BIGINT UNSIGNED NOT NULL, type VARCHAR(50) DEFAULT 'personal', is_primary BOOLEAN DEFAULT FALSE, email VARCHAR(255) NOT NULL, is_verified BOOLEAN DEFAULT FALSE, verified_at TIMESTAMP NULL, metadata JSON NULL, created_at TIMESTAMP NULL, updated_at TIMESTAMP NULL, INDEX (emailable_type, emailable_id), INDEX (type), INDEX (is_primary), INDEX (email) );
Testing
composer test
License
MIT License. See LICENSE for details.