dpl/app/Services/MailFlow/AutoReplyService.php

61 lines
3.1 KiB
PHP

<?php
namespace App\Services\MailFlow;
use App\Models\MailMessage;
use App\Models\User;
class AutoReplyService
{
public function prepare(MailMessage $incoming, array $headers): ?MailMessage
{
if (!Settings::get('auto_reply_enabled')) {
return null;
}
$sender = strtolower($incoming->email);
$own = [strtolower((string) Settings::get('reply_to')), strtolower((string) Settings::get('imap_username')),
strtolower((string) admin_setting('email_from_address', config('mail.from.address')))];
if (!filter_var($sender, FILTER_VALIDATE_EMAIL) || in_array($sender, $own, true)
|| preg_match('/(?:mailer-daemon|postmaster|no-?reply)@/i', $sender)
|| (isset($headers['auto-submitted']) && strtolower($headers['auto-submitted']) !== 'no')
|| isset($headers['list-id']) || isset($headers['x-auto-response-suppress'])
|| in_array(strtolower($headers['precedence'] ?? ''), ['bulk', 'list', 'junk'], true)
|| trim($headers['return-path'] ?? '') === '<>') {
return null;
}
// Binding is optional; never send to an arbitrary incoming Reply-To address.
$user = User::byEmail($sender)->first();
if ($user?->banned) {
return null;
}
if (MailMessage::where('parent_id', $incoming->id)->exists()) {
return null;
}
$day = now()->timezone('Asia/Shanghai')->startOfDay();
if (MailMessage::where('direction', 'out')->where('purpose', 'auto_reply')->where('email', $sender)
->where('created_at', '>=', $day->copy()->timezone(config('app.timezone', 'UTC')))->exists()) {
return null;
}
$outHeaders = ['Auto-Submitted' => 'auto-replied', 'X-Auto-Response-Suppress' => 'All'];
if (preg_match('/^<[^<>\r\n]{1,250}>$/', $headers['message-id'] ?? '')) {
$outHeaders['In-Reply-To'] = $headers['message-id'];
$outHeaders['References'] = $headers['message-id'];
}
$html = view('mail.default.notify', array_replace([
'name' => admin_setting('app_name', 'dpl'), 'url' => admin_setting('app_url', config('app.url')),
'content' => e((string) Settings::get('auto_reply_text')),
], Branding::values()))->render();
// The unique key reserves one reply per sender/day, including concurrent arrivals.
$reply = MailMessage::firstOrCreate(['message_key' => 'reply:' . $day->format('Y-m-d') . ':' . hash('sha256', $sender)], [
'parent_id' => $incoming->id,
'purpose' => 'auto_reply', 'user_id' => $user?->id, 'email' => $sender,
'subject' => Settings::get('auto_reply_subject'),
'expires_at' => $day->copy()->addDay()->timezone(config('app.timezone', 'UTC')),
'payload' => ['to' => $sender, 'subject' => Settings::get('auto_reply_subject'),
'html' => $html, 'attachments' => Branding::attachments(),
'reply_to' => Settings::get('reply_to'), 'headers' => $outHeaders],
]);
return $reply->wasRecentlyCreated ? $reply : null;
}
}