Gửi email bị trì hoãn

Thời gian không đúng? Email được gửi quá sớm? Trì hoãn email có thể hữu ích trong nhiều trường hợp: để bạn có thời gian xem xét hay để quản lý hiệu quả khối lượng công việc của mình - thời gian có thể rất quan trọng. Dưới đây là ba cách bạn có thể trì hoãn việc gửi email và tối ưu hóa hoạt động liên lạc của chính mình.


Triển vọng

Microsoft Outlook cung cấp một tính năng tích hợp cho phép bạn trì hoãn email. Để làm điều này bạn sử dụng chức năng "Tùy chọn > Trì hoãn giao hàng":

Nhược điểm lớn của giải pháp này là (ngay cả trong môi trường Exchange) Outlook phải chạy ở phía máy khách tại thời điểm gửi.

Google

Gmail cung cấp giải pháp tốt hơn một chút ở đây. Để thực hiện việc này, hãy đi tới “Lên lịch gửi” trước khi gửi email.:

Những email đã được gửi cũng có thể được “hoàn tác” trong vòng 30 giây. Để thực hiện việc này, trước tiên bạn hãy đặt cài đặt tương ứng trong “Gọi tất cả cài đặt”.:

Nếu bây giờ bạn gửi email, bạn có thể hoàn tác nó trong khoảng thời gian này ở phía dưới bên trái:

Boomerang

Có những nhà cung cấp dịch vụ trả phí như Boomerang cho Outlook , ngoài lời nhắc hoặc theo dõi tự động, còn cung cấp tính năng gửi email bị trì hoãn.:

Điểm bất lợi ở đây là email kết thúc trên máy chủ của bên thứ ba và phải trả phí khi sử dụng chúng.

PHP

Đủ lý do để tự mình xây dựng thứ gì đó: Bạn cũng có thể đạt được điều mình muốn với sự trợ giúp của một tập lệnh PHP nhỏ. Đầu tiên, chúng ta tạo cấu trúc thư mục trong hộp thư của mình:

Điều này phản ánh thời gian vận chuyển mong muốn. Quy trình làm việc hiện bao gồm việc lưu email đã viết dưới dạng bản nháp thay vì gửi và chuyển nó vào thư mục liên quan. Sau đó chúng tôi cài đặt tất cả các gói PHP cần thiết:

composer require php-imap/php-imap phpmailer/phpmailer vlucas/phpdotenv

Cuối cùng chúng tôi tạo ra một .env-File với dữ liệu truy cập và cài đặt:

HOST_SMTP="xxx"
PORT_SMTP=465
HOST_IMAP="xxx"
PORT_IMAP=993
USERNAME="foo@bar.com"
PASSWORD="xxx"
ENCRYPTION="ssl"
FROM_ADDRESS="foo@bar.com"
FROM_NAME="Foo Bar"
FOLDER_INBOX="INBOX/DELAY"
FOLDER_OUTBOX="Gesendete Elemente"
PHP_EXECUTABLE="/usr/bin/php"

Đoạn script PHP sau sẽ thực hiện phần còn lại:

<?php
require_once __DIR__ . '/vendor/autoload.php';

class MailDelay
{
    private \PhpImap\Mailbox $mailbox;
    private array $folders;

    public function init(): void
    {
        $this->loadEnvironmentVariables();
        $this->initMailbox();
        $this->initFolders();
        $this->processFolders();
    }

    private function loadEnvironmentVariables(): void
    {
        $dotenv = \Dotenv\Dotenv::createImmutable(__DIR__);
        $dotenv->load();
    }

    private function initMailbox(): void
    {
        $this->mailbox = new \PhpImap\Mailbox(
            '{' . $_SERVER['HOST_IMAP'] . ':' . $_SERVER['PORT_IMAP'] . '/imap/ssl}' . $_SERVER['FOLDER_INBOX'],
            $_SERVER['USERNAME'],
            $_SERVER['PASSWORD'],
            sys_get_temp_dir(),
            'UTF-8'
        );
    }

    private function initFolders(): void
    {
        $this->folders = [];
        $folders = $this->mailbox->getMailboxes('*');
        foreach ($folders as $folder) {
            if ($folder['shortpath'] === $_SERVER['FOLDER_INBOX']) {
                continue;
            }
            $this->folders[] = (object) $folder;
        }
    }

    private function processFolders(): void
    {
        foreach ($this->folders as $folder) {
            $this->mailbox->switchMailbox($folder->fullpath);
            $mailIds = $this->mailbox->searchMailbox('ALL');
            foreach ($mailIds as $mailId) {
                $preparedMail = $this->prepareMailData($mailId, $folder->shortpath);
                if (
                    $preparedMail->subject !== 'Dies ist Plain Text' &&
                    strtotime($preparedMail->time_to_send) > strtotime('now')
                ) {
                    continue;
                }
                try {
                    $this->sendMail($preparedMail);
                    echo 'Successfully sent mail #' . $preparedMail->id . '.' . PHP_EOL;
                } catch (\Exception $e) {
                    echo 'Error in sending mail #' . $preparedMail->id . ': ' . $e->getMessage() . PHP_EOL;
                }
            }
        }
        echo 'All mails have been processed.' . PHP_EOL;
    }

    private function prepareMailData(int $id, string $folder): object
    {
        $mail = $this->mailbox->getMail($id, false); // don't mark as unread

        return (object) [
            'id' => (string) $mail->id,
            'to' => $this->formatEmailAddresses($mail->to),
            'cc' => $this->formatEmailAddresses($mail->cc),
            'bcc' => $this->formatEmailAddresses($mail->bcc),
            'subject' => (string) $mail->subject,
            'content_html' => $this->convertEncoding($mail->textHtml),
            'content_plain' => $this->convertEncoding($mail->textPlain),
            'attachments' => $this->determineAttachments($mail->getAttachments()),
            'time_to_send' => $this->determineTimeToSend(explode('/', $folder)[2], $mail->date)
        ];
    }

    private function formatEmailAddresses(?array $addresses): ?array
    {
        if (empty($addresses)) {
            return null;
        }
        return array_map(
            function ($key, $value) {
                return [
                    'email' => $key,
                    'name' => $key === $value ? null : str_replace(' (' . $key . ')', '', $value)
                ];
            },
            array_keys($addresses),
            $addresses
        );
    }

    private function convertEncoding(string $text): string
    {
        return mb_detect_encoding($text, 'UTF-8, ISO-8859-1') !== 'UTF-8'
            ? \UConverter::transcode($text, 'UTF8', 'ISO-8859-1')
            : $text;
    }

    private function determineAttachments(array $attachmentsImap): array
    {
        $attachments = [];
        if (!empty($attachmentsImap)) {
            foreach ($attachmentsImap as $attachment) {
                $attachments[] = [
                    'name' => $attachment->name,
                    'file' => $attachment->filePath,
                    'disposition' => $attachment->disposition,
                    'inline_id' => $attachment->contentId
                ];
            }
        }
        return $attachments;
    }

    private function determineTimeToSend(string $delayTime, string $date): ?string
    {
        $timeToSend = null;
        if ($delayTime === 'THIS EVENING') {
            $timeToSend = date('Y-m-d', strtotime($date)) . ' 18:00:00';
        } elseif ($delayTime === 'THIS NIGHT') {
            $timeToSend =
                date('Y-m-d', strtotime($date . (date('H', strtotime($date)) >= 4 ? ' + 1 day' : ''))) . ' 03:42:00';
        } elseif ($delayTime === 'NEXT MORNING') {
            $timeToSend =
                date('Y-m-d', strtotime($date . (date('H', strtotime($date)) >= 9 ? ' + 1 day' : ''))) . ' 09:00:00';
        } elseif ($delayTime === 'NEXT WEEK') {
            $date = new \DateTime(date('Y-m-d', strtotime($date)));
            $date->modify('next monday');
            $timeToSend = $date->format('Y-m-d') . ' 09:00:00';
        }
        return $timeToSend;
    }

    private function sendMail(object $preparedMail): void
    {
        $mail = new \PHPMailer\PHPMailer\PHPMailer(true);

        $mail->isSMTP();
        $mail->Host = $_SERVER['HOST_SMTP'];
        $mail->Port = $_SERVER['PORT_SMTP'];
        $mail->Username = $_SERVER['USERNAME'];
        $mail->Password = $_SERVER['PASSWORD'];
        $mail->SMTPSecure = $_SERVER['ENCRYPTION'];
        $mail->setFrom($_SERVER['FROM_ADDRESS'], $_SERVER['FROM_NAME']);
        $mail->SMTPAuth = true;
        $mail->SMTPOptions = [
            'tls' => ['verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true],
            'ssl' => ['verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true]
        ];
        $mail->CharSet = 'utf-8';

        $this->addRecipients($mail, $preparedMail->to, 'addAddress');
        $this->addRecipients($mail, $preparedMail->cc, 'addCC');
        $this->addRecipients($mail, $preparedMail->bcc, 'addBCC');

        $mail->isHTML(!empty($preparedMail->content_html));
        $mail->Subject = $preparedMail->subject;

        if (!empty($preparedMail->content_html)) {
            $mail->Body = $preparedMail->content_html;
            $mail->AltBody = !empty($preparedMail->content_plain)
                ? $preparedMail->content_plain
                : strip_tags(str_replace(['<br>', '<br/>', '<br />'], "\r\n", $preparedMail->content_html));
        } else {
            $mail->Body = $preparedMail->content_plain;
        }

        $this->addAttachments($mail, $preparedMail->attachments);

        $mail->send();

        $this->mailbox->moveMail($preparedMail->id, $_SERVER['FOLDER_OUTBOX']);
    }

    private function addRecipients(\PHPMailer\PHPMailer\PHPMailer $mail, ?array $recipients, string $method): void
    {
        if (!empty($recipients)) {
            foreach ($recipients as $recipient) {
                $mail->$method($recipient['email'], $recipient['name']);
            }
        }
    }

    private function addAttachments(\PHPMailer\PHPMailer\PHPMailer $mail, array $attachments): void
    {
        if (!empty($attachments)) {
            foreach ($attachments as $attachment) {
                if (!empty($attachment['file']) && !empty($attachment['name']) && file_exists($attachment['file'])) {
                    if ($attachment['disposition'] === 'attachment') {
                        $mail->addAttachment($attachment['file'], $attachment['name']);
                    } elseif ($attachment['disposition'] === 'inline') {
                        $mail->AddEmbeddedImage(
                            $attachment['file'],
                            $attachment['inline_id'],
                            $attachment['name'],
                            'base64',
                            'image/png'
                        );
                    }
                }
            }
        }
    }
}

$md = new MailDelay();
$md->init();

Để tập lệnh này chạy lặp đi lặp lại, chúng tôi tạo một tệp bash:

#!/usr/bin/env bash
source $(dirname "$0")/.env
"$PHP_EXECUTABLE" $(dirname "$0")/maildelay.php

Sau đó, chúng tôi chạy tập lệnh này 10 phút một lần thông qua công việc định kỳ:

*/10 * * * * /path/to/maildelay/maildelay.sh 2>&1

Giải pháp này có thể tùy chỉnh linh hoạt, thân thiện với quyền riêng tư và miễn phí. Nó hoạt động với mọi tài khoản (tương thích với IMAP). Nhưng bất kể bạn sử dụng chức năng tích hợp trong Outlook hay Gmail, hãy sử dụng các công cụ nâng cao như Boomerang hoặc triển khai giải pháp riêng lẻ - gửi email theo thời gian là một công cụ có giá trị để tăng năng suất của chính bạn.

Trở lại