My Laravel application has a ticket system included, which is sending email notifications.
All emails are built and sent like this one:
public function build()
{
$email_from_name = "Support - " . config('app.name');
$subject = "[" . $this->ticket->id . "] " . $this->ticket->subject . " - " . config('app.name');
return $this->from('[email protected]', $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,
]);
}
Unfortunately, when I get multiple of these emails, no email client (Outlook, Thunderbird, Roundcube,...) shows these emails as thread / conversation. All clients show each email as "new email" thread / conversation.
What specifies, that some emails are one thread / conversation and some not? How can I tell my Laravel application, that these emails are one thread / conversation?
I thought, it just needs to be the same email subject, but it doesn't work.
Thanks to @Yeeooow for the information regarding the RFC 2822
standard: The References and In-Reply-To headers must be set in compliance with the RFC 2822 standard.
Based on these information, I've checked some other email threads / conversations, which I had. All of these used the mentioned References
and In-Reply-To
headers in the same way. With this information, I've started developing to archive the same result.
Due of the fact, that we need to reference to old emails, we need a table, where we can store the Message-ID
of each sent email. I've created this table in my case:
// 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();
});
}
With this table, we're able to store the Message-ID
of each sent email and can reference to which ticket it belongs to. This will help us later also to get only associated Message-ID
s related to this ticket - otherwise, we'll mix up different ticket histories in the same email thread.
Into the reference_id
field, you can optionally store the associated ID of the task:
In your mailable (eg. app\Mail\TicketTextAdded.php
), you can now add the code section $this->withSwiftMessage() {}
into your build()
function to capture the current Message-ID
of this new email and reference to all other emails before as well as store the new Message-ID`:
public function build()
{
$email_from_name = "Support - " . config('app.name');
$subject = "[" . $this->ticket->id . "] " . $this->ticket->subject . " - " . config('app.name');
$email = $this->from('[email protected]', $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: <[email protected]> <[email protected]>'
$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: <[email protected]>'
$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;
}
FYI: You could also change the Message-ID
there, where we've set the custom headers, but this needs to comply to the relevant RFC documents:
$msgId = $swiftmessage->getHeaders()->get('Message-ID');
$msgId->setId(time() . '.' . uniqid('thing') . '@example.org');
Further information: https://swiftmailer.symfony.com/docs/headers.html#id-headers
Hopefully, I could help somebody else with these information. :)