wnx/laravel-sends

跟踪您的Laravel应用程序中的发出的电子邮件。

v2.4.2 2024-02-03 14:53 UTC

This package is auto-updated.

Last update: 2024-09-03 20:27:17 UTC


README

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

此包帮助您跟踪Laravel应用程序中的发出的电子邮件。此外,您可以将模型与发出的电子邮件关联起来。

以下是一些简短的示例,展示您可以做什么

// 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));

在您的应用程序中,您可以获取用户或产品的所有已发出的电子邮件。

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

或者您可以获取给定Mailable类的所有已发出的电子邮件。

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

每个Send-模型包含以下信息

  • mailable类的FQN(完全限定名)
  • 主题
  • 发件人地址
  • 回复地址
  • 收件人地址
  • cc地址
  • bcc地址

此外,sends-表还有以下列,这些列可以由您的应用程序填充(了解更多)。

  • delivered_at
  • last_opened_at
  • 打开次数
  • 点击次数
  • last_clicked_at
  • complained_at
  • bounced_at
  • permanent_bounced_at
  • rejected_at

安装

您可以通过composer安装此包

composer require wnx/laravel-sends

然后,发布并运行迁移

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

可选地,您可以使用以下命令发布配置文件

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

这是发布配置文件的内容

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.
         * Set this to `Message-ID`, if you want to use the Message ID as the unique ID identifing the sent mail.
         */
        'send_uuid' => env('SENDS_HEADERS_SEND_UUID', 'X-Laravel-Send-UUID'),
    ],
];

用法

安装完成后,更新您的应用程序的EventServiceProvider以监听MessageSent事件。将StoreOutgoingMailListener-类作为监听器添加。

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

现在,所有由mailables或通知创建的发出的电子邮件的元数据现在都存储在sends-表中。(注意,您只能将模型与mailables关联;但不能与通知关联)

阅读以下内容以了解如何存储名称以及如何将模型与Mailable类关联。

在Send模型上存储Mailable类名

默认情况下,事件监听器存储邮件的主题和收件人地址。这很棒,但我们可以做得更好。对于您的应用程序来说,了解哪个Mailable类触发了发送的电子邮件可能是有益的。

要存储此信息,请像下面那样将StoreMailables-trait添加到您的Mailable类中。现在您可以访问一些辅助方法。

根据您如何编写Mailables,有不同方法来使用这些新方法。在您的Mailable的build方法中调用storeClassName-方法。

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");
    }
}

如果您使用Laravel v9.35中引入的Mailable语法,您可以在headers方法中使用$this->storeClassName(),或者将$this->getMailClassHeader()->toArray()传递给Header对象。

class ProductReviewMail extends Mailable
{
    use SerializesModels;
    use StoreMailables;

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

    // ...

    /**
     * @return \Illuminate\Mail\Mailables\Headers
     */
    public function headers()
    {
        // Call storeClassName() and let the package take care of adding the
        // header to the outgoing mail.
        $this->storeClassName();

        // Or – if you want more control – use the getMailClassHeader() method
        // to get a Header instance and merge it with your own headers.
        return new Headers(
            text: [
                'X-Custom-Header' => 'Custom Value',
                ...$this->getMailClassHeader()->toArray(),
            ],
        );
    }
}

此方法将添加一个X-Laravel-Mail-Class-头到发出的电子邮件中,其中包含Mailable类的完全限定名(FQN)作为加密字符串。(例如:App\Mails\ProductReviewMail)。更新SENDS_HEADERS_MAIL_CLASS-env变量以调整头名称。(有关详细信息,请参阅配置)。

然后,包的事件监听器将查找该头,解密值并将其存储在数据库中。

将Sends与相关模型关联

现在您已经记录了所有发出的电子邮件,如果您能通过关联的模型访问这些记录,岂不是很好?在上面的例子中,我们向用户发送了一个 ProductReviewMail。您想查看为特定的 Product-模型发送了多少封电子邮件吗?

要实现这一点,您需要在模型中添加 HasSends 接口和 HasSendsTrait 特性。

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

class Product extends Model implements HasSends
{
    use HasSendsTrait;
}

现在您可以在 build() 方法内调用 associateWith() 方法。将您想与 Mailable 类关联的模型作为参数传递给方法。除了作为参数传递模型外,您还可以将它们作为数组传递。

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");
    }
}

如果您使用的是 Laravel v9.35 中引入的 Mailable 语法,您也可以在 headers 方法中调用 associateWith()getMailModelsHeader()

class ProductReviewMail extends Mailable
{
    use SerializesModels;
    use StoreMailables;

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

    // ...

    /**
     * @return \Illuminate\Mail\Mailables\Headers
     */
    public function headers()
    {
        // Call associateWith() and the package automatically associates the public
        // properties with this mailable.
        $this->associateWith($this->user);
        $this->associateWith([$this->product]);

        // Or – if you want more control – use the getMailModelsHeader() method
        // to get a Header instance and merge it with your own headers.
        return new Headers(
            text: [
                'X-Custom-Header' => 'Custom Value',
                ...$this->getMailModelsHeader($this->user)->toArray(),
                ...$this->getMailModelsHeader([$this->product])->toArray(),
            ],
        );
    }
}

现在您可以通过产品的 sends 关联关系访问发送出去的电子邮件。

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

自动关联模型与 Mailable

如果您没有向 associateWith 方法传递参数,则该软件包会自动将实现 HasSends 接口的所有公共属性与 Mailable 类关联。🪄

在下面的示例中,ProductReviewMail-Mailable 将自动与 $product 模型关联,因为 Product 实现了 HasSends 接口。由于 $user 模型被声明为私有属性,因此将忽略它。

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");
    }
}

如果您使用的是 Laravel v9.35 中引入的 Mailable 语法,您也可以在 headers 方法中调用 associateWith()getMailModelsHeader()

class ProductReviewMail extends Mailable
{
    use SerializesModels;
    use StoreMailables;

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

    // ...

    /**
     * @return \Illuminate\Mail\Mailables\Headers
     */
    public function headers()
    {
        // Call associateWith() and the package automatically associates the public
        // properties with this mailable.
        $this->associateWith();

        // Or – if you want more control – use the getMailModelsHeader() method
        // to get a Header instance and merge it with your own headers.
        return new Headers(
            text: [
                'X-Custom-Header' => 'Custom Value',
                ...$this->getMailModelsHeader()->toArray(),
            ],
        );
    }
}

将自定义消息 ID附加到邮件

如果您通过 AWS SES 或类似服务发送电子邮件,您可能希望在将来识别发送的电子邮件(例如,当“已投递”事件的 webhook 被发送到您的应用程序时)。

该软件包附带了一个事件监听器来帮助您。更新 EventServiceProvider 以监听 MessageSending 事件,并将 AttachSendUuidListener 添加为监听器。将一个 X-Laravel-Message-UUID 标头附加到所有发出的电子邮件。该标头包含一个 UUID 值。(此值不能与 RFC 2392 中定义的 Message-ID 进行比较)
然后您可以在应用程序中使用 X-Laravel-Message-UUID$send->uuid 的值。

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

(如果您想在数据库中存储 Message-ID 的值,请不要添加事件监听器,而是更新 SENDS_HEADERS_SEND_UUID 环境变量到 Message-ID。然后,StoreOutgoingMailListener 将将 Message-ID 存储到数据库中。)

存储邮件内容

默认情况下,该软件包不会存储发送出去的电子邮件的内容。通过将 sends.store_content 配置值设置为 true,所有发出的邮件的正文都存储在 sends 数据库表的 content 列中。

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

自定义存储在发送模型中的属性

如果您需要与 Send 模型一起存储更多属性,您可以扩展 StoreOutgoingMailListener 并重写 getSendAttributes 方法。

例如,假设我们想将 audience 值与每封发送出去的电子邮件一起存储。我们创建一个新的事件监听器,名为 CustomStoreOutgoingMailListener,并将其用作 MessageSent 事件的监听器。

我们的 EventServiceProvider 将看起来像这样。

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

监听器本身将如下所示。我们扩展 Wnx\Sends\Listeners\StoreOutgoingMailListener 并重写 getSendAttributes。我们将 $defaultAttributes 与我们想要存储的自定义属性合并。在下面的示例中,我们存储了一个 audience 值。

<?php

namespace App\Listeners;

use Illuminate\Mail\Events\MessageSent;
use Wnx\Sends\Listeners\StoreOutgoingMailListener;

class CustomStoreOutgoingMailListener extends StoreOutgoingMailListener
{
    protected function getSendAttributes(MessageSent $event, array $defaultAttributes): array
    {
        return array_merge($defaultAttributes, [
            'audience' => $this->getAudience($event),
        ]);
    }

修剪发送模型

默认情况下,Send 模型将永久保留在您的数据库中。如果您的应用程序每天发送数千封电子邮件,您可能希望几天或几个月后修剪记录。

为此,请使用 Laravel 的 prunable 功能。

在您的 app/Models 中创建一个新的继承自 Wnx\Sends\Models\SendSend 模型。然后添加 Prunable 特性,并按需设置 prunable() 方法。以下示例删除了所有超过1个月的 Send 模型。

// 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());
    }
}

可选地,您还可以更新配置,以便包内部使用您的 Send 模型。

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

sends 表的进一步使用

如您所注意到的,sends 表包含的列比当前包填充的列要多。这是有意为之的。

鼓励您编写自己的应用程序逻辑来填充这些目前为空的列。例如,如果您通过 AWS SES 发送电子邮件,我强烈建议您使用 renoki-co/laravel-aws-webhooks 包来处理 AWS SNS 通知。

处理 "Delivered" 事件的控制器可能如下所示。

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();
    }
}

测试

composer test

变更日志

有关最近更改的更多信息,请参阅 变更日志

贡献

有关详细信息,请参阅 贡献指南

安全漏洞

有关如何报告安全漏洞的详细信息,请参阅 我们的安全策略

鸣谢

许可协议

MIT 许可协议 (MIT)。有关更多信息,请参阅 许可文件