A package to keep track of outgoing emails in your Laravel application.

Overview

Keep track of outgoing emails and associate sent emails with Eloquent models

Latest Version on Packagist run-tests Check & fix styling Total Downloads

This package helps you to keep track of outgoing emails in your Laravel application. In addition, you can associate Models to the sent out emails.

Here are a few short examples what you can do:

// An email is sent to $user. The passed $product and $user models will be 
// associated with the sent out email generated by the `ProductReviewMail` mailable.
Mail::to($user)->send(new ProductReviewMail($product, $user));

In your application, you can then fetch all sent out emails for the user or the product.

$user->sends()->get();
$product->sends()->get();

Or you can fetch all sent out emails for the given Mailable class.

Send::forMailClass(ProductReviewMail::class)->get();

Each Send-model holds the following information:

  • FQN of mailable class
  • subject
  • from address
  • reply to address
  • to address
  • cc adresses
  • bcc adresses

Additionally, the sends-table has the following columns which can be filled by your own application (learn more).

  • delivered_at
  • last_opened_at
  • opens
  • clicks
  • last_clicked_at
  • complained_at
  • bounced_at
  • permanent_bounced_at
  • rejected_at

Installation

You can install the package via composer:

composer require wnx/laravel-sends

Then, publish and run the migrations:

php artisan vendor:publish --tag="sends-migrations"
php artisan migrate

Optionally, you can publish the config file with:

php artisan vendor:publish --tag="sends-config"

This is the contents of the published config file:

return [
    /*
     * The fully qualified class name of the send model.
     */
    'send_model' => \Wnx\Sends\Models\Send::class,

    /**
     * If set to true, the content of sent mails is saved to the database.
     */
    'store_content' => env('SENDS_STORE_CONTENT', false),

    'headers' => [
        /**
         * Header containing the encrypted FQN of the mailable class.
         */
        'mail_class' => env('SENDS_HEADERS_MAIL_CLASS', 'X-Laravel-Mail-Class'),

        /**
         * Header containing an encrypted JSON object with information which
         * Eloquent models are associated with the mailable class.
         */
        'models' => env('SENDS_HEADERS_MAIL_MODELS', 'X-Laravel-Mail-Models'),

        /**
         * Header containing unique ID of the sent out mailable class.
         */
        'send_uuid' => env('SENDS_HEADERS_SEND_UUID', 'X-Laravel-Send-UUID'),
    ],
];

Usage

After the installation is done, update your applications EventServiceProvider to listen to the MessageSent event. Add the StoreOutgoingMailListener-class as a listener.

// app/Providers/EventServiceProvider.php
protected $listen = [
    // ...
    \Illuminate\Mail\Events\MessageSent::class => [
        \Wnx\Sends\Listeners\StoreOutgoingMailListener::class,
    ],
]

The metadata of all outgoing emails created by mailables or notifications is now being stored in the sends-table. (Note that you can only associate models to mailables; but not to notifiations)

Read further to learn how to store the name and how to associate models with a Mailable class.

Store Mailable class name on Send Model

By default the Event Listener stores the mails subject and the recipient adresses. That's nice, but we can do better. It can be beneficial for your application to know which Mailable class triggered the sent email.

To store this information, add the StoreMailables-trait to your Mailable classes like below. Then call the storeClassName()-method inside the build-method.

class ProductReviewMail extends Mailable
{
    use SerializesModels;
    use StoreMailables;

    public function __construct(public User $user, public Product $product)
    {
    }

    public function build()
    {
        return $this
            ->storeClassName()
            ->view('emails.products.review')
            ->subject("$this->product->name waits for your review");
    }
}

The method will add a X-Laravel-Mail-Class-header to the outgoing email containing the fully qualified name (FQN) of the Mailable class as an encrypted string. (eg. App\Mails\ProductReviewMail). Update the SENDS_HEADERS_MAIL_CLASS-env variable to adjust the header name. (See config for details).

The package's event listener will then look for the header, decrypt the value and store it in the database.

Associate Sends with Related Models

Now that you already keep track of all outgoing emails, wouldn't it be great if you could access the records from an associated Model? In the example above we send a ProductReviewMail to a user. Wouldn't it be great to see how many emails you have sent for a given Product-model?

To achieve this, you have to add the HasSends-interface and the HasSendsTrait trait to your models.

use Wnx\Sends\Contracts\HasSends;
use Wnx\Sends\Support\HasSendsTrait;

class Product extends Model implements HasSends
{
    use HasSendsTrait;
}

You can now call the associateWith()-method within the build()-method. Pass the Models you want to associate with the Mailable class to the method as arguments. Instead of passing the Models as arguments, you can also pass them as an array.

class ProductReviewMail extends Mailable
{
    use SerializesModels;
    use StoreMailables;

    public function __construct(private User $user, private Product $product)
    {
    }

    public function build()
    {
        return $this
            ->storeClassName()
            ->associateWith($this->product)
            // ->associateWith([$this->product])
            ->view('emails.products.review')
            ->subject("$this->user->name, $this->product->name waits for your review");
    }
}

You can now access the sent out emails from the product's send-relationship.

$product->sends()->get();

Automatically associate Models with Mailables

If you do not pass an argument to the associateWith-method, the package will automatically associate all public properties which implement the HasSends-interface with the Mailable class. 🪄

In the example below, the ProductReviewMail-Mailable will automatically be associated with the $product Model, as Product implements the HasSends interface. The $user model will be ignored, as it's declared as a private property.

class ProductReviewMail extends Mailable
{
    use SerializesModels;
    use StoreMailables;

    public function __construct(
        private User $user, 
        public Product $product
    ) { }

    public function build()
    {
        return $this
            ->associateWith()
            ->view('emails.products.review')
            ->subject("$this->user->name, $this->product->name waits for your review");
    }
}

Attach custom Message ID to Mails

If you're sending emails through AWS SES or a similar service, you might want to identify the sent email in the future (for example when a webhook for the "Delivered"-event is sent to your application).

The package comes with an event listener helping you here. Update the EventServiceProvider to listen to the MessageSending event and add the AttachSendUuidListener as a listener. A X-Laravel-Message-UUID header will be attached to all outgoing emails. The header contains a UUID value. (This value can not be compared to the Message-ID defined in RFC 2392)
You can then use the value of X-Laravel-Message-UUID or $send->uuid later in your application.

// app/Providers/EventServiceProvider.php
protected $listen = [
    // ...
    \Illuminate\Mail\Events\MessageSending::class => [
        \Wnx\Sends\Listeners\AttachSendUuidListener::class,
    ],
]

(If you want to store the value of Message-ID in your database, do not add the event listener but update the SENDS_HEADERS_SEND_UUID-env variable to Message-ID. The StoreOutgoingMailListener will then store the Message-ID in the database.)

Store Content of Mails

By default, the package does not store the content of sent out emails. By setting the sends.store_content configuration value to true, the body of all outgoing mails is stored in the content-column in the sends database table.

/**
 * If set to true, the contet of sent mails is saved to the database.
 */
'store_content' => true,
SENDS_STORE_CONTENT=true

Prune Send Models

By default, Send-models are kept forever in your database. If your application sends thousands of emails per day, you might want to prune records after a couple of days or months.

To do that, use the prunable feature of Laravel.

Create a new Send-model in your app/Models that extends Wnx\Sends\Models\Send. Then add the Prunable-trait and set up the prunable()-method to your liking. The example below deletes all Send-models older than 1 month.

// app/Models/Send.php
namespace App\Models;

use Illuminate\Database\Eloquent\Prunable;
use Wnx\Sends\Models\Send as BaseSend;

class Send extends BaseSend
{
    use Prunable;

    public function prunable()
    {
        return static::where('created_at', '<=', now()->subMonth());
    }
}

Optionally you can also update the configuration, so that the package internally uses your Send model.

// config/sends.php
/*
 * The fully qualified class name of the send model.
 */
'send_model' => \App\Models\Send::class,

Further Usage of the sends-table

As you might have noticed, the sends-table comes with more columns than that are currently filled by the package. This is by design.

You are encouraged to write your own application logic to fill these currently empty columns. For example, if you're sending emails through AWS SES, I highly encourage you to use the renoki-co/laravel-aws-webhooks package to handle AWS SNS webhooks.

A controller that handles the "Delivered" event might look like this.

class AwsSnsSesWebhookController extends SesWebhook {
    protected function onDelivery(array $message)
    {
        $uuidHeader = collect(Arr::get($message, 'mail.headers', []))
            ->firstWhere('name', config('sends.headers.send_uuid'));

        if ($uuidHeader === null) {
            return;
        }

        $send = Send::forUuid($uuidHeader['value'])->first();

        if ($send === null) {
            return;
        }

        $send->delivered_at = now();
        $send->save();
    }
}

Testing

composer test

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The MIT License (MIT). Please see License File for more information.

Comments
  • Message AttachCustomMessageIdListener Stored in DB Not Same As MessageID Sent from Mailgun

    Message AttachCustomMessageIdListener Stored in DB Not Same As MessageID Sent from Mailgun

    Prior to using the AttachCustomMessageIdListener the message id wouldn't save in the database, but when using the AttachCustomMessageIdListener, the message id saved in the database is not the same as the one found in Mailgun.

    Currently, the message ids found on Mailgun all end with @swift.generated but the numbers before the '@' are not similar to the ones stored in the sends database.

    opened by mprythero 4
  • Usage of custom model

    Usage of custom model

    When using a custom model (extending the default one). This package automatically uses id as column name. Except not everyone uses the default laravel naming conventions.

    changelog:fixed 
    opened by Mul-tiMedia 1
  • Add Support for new Mailable syntax

    Add Support for new Mailable syntax

    This PR tackles #12 and adds new methods to support the new Mailables syntax that has been added in Laravel v9.35.

    TODOs

    • [x] Add fake Mailables used in tests
    • [x] Add Tests
    • [x] Refactor StoresMailables-Trait
    • [x] Update docs in README
    • [x] Finalise public API of new methods
    changelog:added 
    opened by stefanzweifel 0
  • Support new Mailable syntax

    Support new Mailable syntax

    Laravel 9.35 added a new syntax to write Mailables (https://github.com/laravel/framework/pull/44462#issue-1396874380).

    I would like to update "laravel-sends", so that this new syntax is also supported and the header can automatically be attached.

    • Links to docs: https://laravel.com/docs/9.x/mail#headers
    enhancement 
    opened by stefanzweifel 0
  • Store Content of outgoing mail messages in the database

    Store Content of outgoing mail messages in the database

    On Twitter the question came up, why the package doesn't store the content/body of outgoging mails. I honestly didn't think about that at all, as my primary usecase for the packge is to keep track of meta data.

    Implementing this feature is trivial, as the Swif_Message instance exposes a getBody() method.

    The feature is hidden behind a new configuration flag (sends.store_content) and has to be enabled by the consumer of the package. I will keep it that way in future releases, as I would like to prevent users from shooting themself in the foot. Storing the body of outgoing mails can blow up the size of a database pretty quickly.

    Questions to clarify

    • [x] Should I create an additional migration to add the content-column? Or should this be the concern of the consumer? (Like done in the UPGRADE guide for v0 to v1) → Will make this a new major version. The package is too young to already add multiple migrations to it. Will provide instructions in the upgrade guide
    changelog:added 
    opened by stefanzweifel 0
  • Rename `custom_message_id` column to `uuid`; update related code

    Rename `custom_message_id` column to `uuid`; update related code

    To reduce confusion, I've decided to rename the custom_message_id-column on the sends database table to uuid. The X-Laravel-Message-ID-header has been renamed to X-Laravel-Send-UUID too. Same goes for configuration names and Eloquent query scopes.

    The term "Message ID" originates from RFC 2392 and has a well defined format. The package doesn't follow that format and just uses a UUID value.

    closes #3

    TODOs

    • [x] Add UPGRADING.md to document how to upgrade vom v0.1 to v1.0
    changelog:changed 
    opened by stefanzweifel 0
  • Update associateWith signature to either pass an array or multiple HasSends models as arguments

    Update associateWith signature to either pass an array or multiple HasSends models as arguments

    This PR updates the signature of the associateWith()-method, so that either an array or multiple HasSends-models can be passed as the argument to the method.

    This change mimick the behavior often seen in the Laravel framework, where either an array or a list can be passed to a method. The list of arguments is internally transformed into an array.

    TODO

    • [x] Update README/docs
    changelog:changed 
    opened by stefanzweifel 0
Releases(v2.3.0)
Owner
Stefan Zweifel
Full Stack Developer working with Laravel, Vue.js, Tailwind CSS and alpine.js.
Stefan Zweifel
Laravel Seeable - Keep track of the date and time a user was last seen.

Laravel Seeable This package makes it easy to keep track of the date and time a user was last seen. Installation Install this package. composer requir

Zep Fietje 29 Dec 26, 2022
Easily capture every incoming request and the corresponding outgoing response in your Laravel app.

Easily capture every incoming request and the corresponding outgoing response in your Laravel app. This package is designed to work only with the Lara

Mark Townsend 22 Nov 15, 2022
Catch incoming emails in your Laravel application

Laravel Mailbox ?? Handle incoming emails in your Laravel application. Mailbox::from('{username}@gmail.com', function (InboundEmail $email, $username)

Beyond Code 918 Jan 2, 2023
This package allows you to easily track your laravel jobs!

Trackable Jobs For Laravel This package allows you to track your laravel jobs! Using this package, you can easily persist the output and the status of

Mateus Junges 220 Dec 25, 2022
This package provides a Filament resource to view all Laravel sent emails.

This package provides a Filament resource to view all Laravel outgoing emails. It also provides a Model for the database stored emails. Installation Y

Ramón E. Zayas 22 Jan 2, 2023
Ensure your Laravel applications keep a normal pulse

Ensure your Laravel applications keep a normal rhythm Laravel Defibrillator helps you ensure that aspects of your application that should be running a

Michael Dyrynda 148 Dec 20, 2022
A Laravel package to help track user onboarding steps.

Onboard A Laravel package to help track user onboarding steps. Installation: Install the package via composer composer require calebporzio/onboard Reg

Caleb Porzio 440 Dec 17, 2022
Laravel plugin to track your users logins and alert when a suspicious login occurs

Laravel Suspicious Logins Detect suspicious logins for standard Laravel authentication (base Laravel, Jetstream, etc) and notify a list of administrat

Advent Development 74 May 1, 2022
Simple project to send bulk comma-separated emails using laravel and messenger module from quick admin panel generator.

About Laravel Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experie

Novath Thomas 1 Dec 1, 2021
Thunder is an advanced Laravel tool to track user consumption using Cashier's Metered Billing for Stripe. ⚡

⚡ Thunder Thunder is an advanced Laravel tool to track user consumption using Cashier's Metered Billing for Stripe. ⚡ ?? Supporting If you are using o

Renoki Co. 10 Nov 21, 2022
🕵🏻‍♂️  The easiest way to respect the "do not track" header in Laravel

trackable The easiest way to respect the "do not track" header in Laravel Installation composer require s360digital/trackable API Trackable will expos

s360 2 Oct 7, 2022
Jetstrap is a lightweight laravel 8 package that focuses on the VIEW side of Jetstream / Breeze package installed in your Laravel application

A Laravel 8 package to easily switch TailwindCSS resources generated by Laravel Jetstream and Breeze to Bootstrap 4.

null 686 Dec 28, 2022
A Laravel chat package. You can use this package to create a chat/messaging Laravel application.

Chat Create a Chat application for your multiple Models Table of Contents Click to expand Introduction Installation Usage Adding the ability to partic

Tinashe Musonza 931 Dec 24, 2022
Laravel package to find performance bottlenecks in your laravel application.

Laravel Meter Laravel Meter monitors application performance for different things such as requests, commands, queries, events, etc and presents result

Sarfraz Ahmed 230 Dec 27, 2022
Laravel comments - This package enables to easily associate comments to any Eloquent model in your Laravel application

Laravel comments - This package enables to easily associate comments to any Eloquent model in your Laravel application

Rubik 4 May 12, 2022
A Laravel package helps you add a complete real-time messaging system to your new / existing application with only one command.

A Laravel package helps you add a complete real-time messaging system to your new / existing application with only one command.

Munaf Aqeel Mahdi 1.7k Jan 5, 2023
Laravel package that converts your application into a static HTML website

phpReel Static Laravel Package phpReel Static is a simple Laravel Package created and used by phpReel that converts your Laravel application to a stat

phpReel 16 Jul 7, 2022
A simple package allowing for consistent API responses throughout your Laravel application

Laravel API Response Helpers A simple package allowing for consistent API responses throughout your Laravel application. Requirements PHP ^7.4 | ^8.0

F9 Web Ltd. 441 Jan 5, 2023
Gretel is a Laravel package for adding route-based breadcrumbs to your application.

Gretel Laravel breadcrumbs right out of a fairy tale. Gretel is a Laravel package for adding route-based breadcrumbs to your application. Defining Bre

Galahad 131 Dec 31, 2022