Laravel 5.6:如何发送电子邮件,客户端将它们显示为线程/对话?

use*_*991 1 email email-threading laravel laravel-5

我的 Laravel 应用程序包含一个票证系统,用于发送电子邮件通知。

所有电子邮件都是这样构建和发送的:

public function build()
{
    $email_from_name = "Support - " . config('app.name');
    $subject = "[" . $this->ticket->id . "] " . $this->ticket->subject . " - " . config('app.name');

    return $this->from('support@example.com', $email_from_name)
                    ->subject($subject)
                    ->markdown('emails.customer.ticket.comment_added')
                        ->with([
                            'nickname' => $this->user->nickname,
                            'ticket_id' => $this->ticket->id,
                            'ticket_subject' => $this->ticket->subject,
                            'ticket_text' => $this->ticket_comments->text,
                        ]);
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,当我收到多封这样的电子邮件时,没有电子邮件客户端(Outlook、Thunderbird、Roundcube...)将这些电子邮件显示为线程/对话。所有客户端将每封电子邮件显示为“新电子邮件”线程/对话。

有什么规定,有些电子邮件是一个线程/对话,有些则不是?我如何告诉我的 Laravel 应用程序这些电子邮件是一个线程/对话?

我想,它只需要是相同的电子邮件主题,但它不起作用。

use*_*991 6

感谢@Yeeooow 提供有关该标准的信息RFC 2822The References and In-Reply-To headers must be set in compliance with the RFC 2822 standard.

根据这些信息,我检查了我拥有的一些其他电子邮件线程/对话。所有这些都以相同的方式使用提到的References和标题。In-Reply-To有了这些信息,我开始开发以存档相同的结果。

由于我们需要引用旧电子邮件,因此我们需要一个表来存储Message-ID每封发送的电子邮件。我在我的案例中创建了这个表:

// Table: ticket_message_ids
public function up()
{
    Schema::create('ticket_message_ids', function (Blueprint $table) {
        $table->increments('id');
        $table->integer('ticket_id')->unsigned();
        $table->integer('reference_id')->unsigned(); // Optional; You may remove it or make it ->nullable()
        $table->string('message_id');
        $table->timestamps();
    });
}
Run Code Online (Sandbox Code Playgroud)

通过此表,我们可以存储Message-ID每封已发送电子邮件的编号,并可以参考它属于哪个票证。这也将帮助我们稍后仅获取Message-ID与此票证相关的关联 - 否则,我们将在同一电子邮件线程中混合不同的票证历史记录。

您可以选择在该reference_id字段中存储任务的关联 ID:

  • 添加了票证文本
  • 已执行的票证操作(例如支持者、优先级、标题或状态已更改)

在您的可邮寄邮件(例如app\Mail\TicketTextAdded.php)中,您现在可以将代码部分添加$this->withSwiftMessage() {}到您的build()函数中,以捕获此新电子邮件的当前内容Message-ID并引用之前的所有其他电子邮件以及存储新的消息 ID`:

public function build()
{
    $email_from_name = "Support - " . config('app.name');
    $subject = "[" . $this->ticket->id . "] " . $this->ticket->subject . " - " . config('app.name');

    $email = $this->from('support@example.com', $email_from_name)
                    ->subject($subject)
                    ->markdown('emails.customer.ticket.comment_added')
                        ->with([
                            'nickname' => $this->user->nickname,
                            'ticket_id' => $this->ticket->id,
                            'ticket_subject' => $this->ticket->subject,
                            'ticket_text' => $this->ticket_comments->text,
                        ]);

    // Access underlaying Swift message
    $this->withSwiftMessage(function ($swiftmessage) {
        // Get all Message-IDs associated to this specific ticket
        $message_ids = TicketMessageIds::where('ticket_id', '=', $this->ticket->id)->get();

        // Build RFC2822 conform 'References' header
        // Example: 'References: <4bcf5a806f795b86fb2ba7238c78983c@swift.generated> <ab7898365c4aa91bfe010d4e1e8da377@swift.generated>'
        $header_references = "";            
        foreach($message_ids as $message_id) {
            if(empty($header_references)) {
                $header_references = $message_id->message_id;
            } else {
                $header_references = $header_references . " " . $message_id->message_id;
            }
        }

        // Build RFC2822 conform 'In-Reply-To' header
        // Example: 'In-Reply-To: <ab7898365c4aa91bfe010d4e1e8da377@swift.generated>'
        $header_in_reply_to = TicketMessageIds::where('ticket_id', '=', $this->ticket->id)->orderBy('id', 'DESC')->get(['message_id'])->first()->message_id;

        // Add required custom headers with above values
        $headers = $swiftmessage->getHeaders();
        // 'X-Mailer' header is not required for this purpose
        // This header sets only a name for the client, which sent this message (typical values: Outlook 2016, PHPMailer v6.0.5,...)
        $headers->addTextHeader('X-Mailer', config('app.name') . ' (' . config('app.url') . ')');
        if(!empty($header_references)) {
            $headers->addTextHeader('References', $header_references);
        }
        $headers->addTextHeader('In-Reply-To', $header_in_reply_to);

        TicketMessageIds::create([
            'ticket_id' => $this->ticket->id,
            'message_id' => '<'.$swiftmessage->getId().'>'
        ]);
    });

    return $email;
}
Run Code Online (Sandbox Code Playgroud)

仅供参考:您还可以更改Message-ID我们设置自定义标头的位置,但这需要遵守相关的 RFC 文档:

$msgId = $swiftmessage->getHeaders()->get('Message-ID');
$msgId->setId(time() . '.' . uniqid('thing') . '@example.org');
Run Code Online (Sandbox Code Playgroud)

更多信息:https://swiftmailer.symfony.com/docs/headers.html#id-headers

希望我可以通过这些信息帮助其他人。:)