Gửi email một PDF được tạo dưới dạng tệp đính kèm của mailer
Tổng quan nhanh
Phần tiêu đề “Tổng quan nhanh”Gửi email một hóa đơn, biên lai, hay báo cáo là một trong những việc phổ biến nhất bạn làm với một PDF được tạo. Cách gọn gàng để làm điều đó là dựng tài liệu, lấy các byte thô của nó, và trao thẳng các byte đó cho API đính kèm của mailer. Bạn không cần một tệp tạm trên đĩa ở bất kỳ thời điểm nào.
Đây là một how-to. Nó giả định bạn đã biết cách gửi mail trong framework của mình.
Phía NextPDF là một lệnh gọi duy nhất:
Document::getPdfData() trả về các byte Portable Document Format (PDF) thô dưới
dạng một chuỗi. Phía đính kèm hoàn toàn thuộc về mailer của bạn — hướng dẫn này
dùng Attachment::fromData() của Laravel và Email::attach() của Symfony Mailer.
NextPDF không đi kèm một mail helper. Không có phương thức “email this PDF” trên một tài liệu, và bạn nên nghi ngờ bất kỳ ví dụ nào cho thấy một cái. API đính kèm luôn là của framework của bạn.
Trang này là đối ứng gửi-đi của việc nhúng tệp vào bên trong một PDF. Hướng dẫn đó đính kèm tệp vào trong PDF dưới dạng các luồng được nhúng; hướng dẫn này đính kèm PDF hoàn chỉnh vào một email. Chúng là các thao tác khác nhau — đừng lẫn lộn hai cái.
Lấy các byte PDF thô
Phần tiêu đề “Lấy các byte PDF thô”Dù bạn làm gì khác, bước NextPDF vẫn như nhau: tạo ra các byte.
<?php
declare(strict_types=1);
use NextPDF\Core\Document;
// Standalone entrypoint: the static factory wires the default dependencies.// (The bare `new Document(...)` constructor requires injected collaborators.)$document = Document::createStandalone();$document->addPage();$document->cell(0, 10, 'Invoice #1042', newLine: true);
// Raw PDF bytes, built in memory. No file is written.$bytes = $document->getPdfData();getPdfData() dựng tài liệu và trả về các byte của nó dưới dạng một chuỗi. Nó
không ghi gì xuống đĩa và không gửi header Hypertext Transfer Protocol (HTTP) nào,
đó chính xác là điều bạn muốn cho một tệp đính kèm.
Nếu bạn chỉ giữ tài liệu qua kiểu NextPDF\Contracts\PdfDocumentInterface
(ví dụ, một giá trị mà tích hợp framework trao cho bạn), hãy dùng tương đương ở
cấp hợp đồng thay vì vậy:
use NextPDF\Contracts\OutputDestination;
$bytes = $document->output(dest: OutputDestination::String);output(dest: OutputDestination::String) được khai báo trên
PdfDocumentInterface và trả về cùng các byte thô mà không phát ra bất kỳ header
nào. Hãy dùng getPdfData() khi bạn giữ một NextPDF\Core\Document cụ thể, và
dạng output(...) khi bạn chỉ có interface.
Nếu bạn dùng tích hợp Laravel hoặc Symfony, hãy phân giải một tài liệu mới từ container thay vì khởi tạo một cái trực tiếp — xem Trả về một PDF được tạo từ một controller để biết lối phân giải trong mỗi framework. Mọi thứ bên dưới hoạt động như nhau bất kể bạn lấy tài liệu bằng cách nào.
Laravel: đính kèm các byte trong bộ nhớ vào một Mailable
Phần tiêu đề “Laravel: đính kèm các byte trong bộ nhớ vào một Mailable”Attachment::fromData() của Laravel nhận một callback trả về các byte thô, cộng
với một tên tệp. Không có tệp tạm. Hãy hiện thực attachments() trên Mailable của
bạn và trả về một Attachment.
<?php
declare(strict_types=1);
namespace App\Mail;
use App\Models\Invoice;use Illuminate\Mail\Mailable;use Illuminate\Mail\Mailables\Attachment;use Illuminate\Mail\Mailables\Content;use Illuminate\Mail\Mailables\Envelope;use NextPDF\Core\Document;
final class InvoiceMail extends Mailable{ public function __construct(private readonly Invoice $invoice) {}
public function envelope(): Envelope { return new Envelope(subject: "Invoice #{$this->invoice->number}"); }
public function content(): Content { return new Content(markdown: 'mail.invoice'); }
/** @return array<int, Attachment> */ public function attachments(): array { return [ Attachment::fromData(fn (): string => $this->buildPdf(), "invoice-{$this->invoice->number}.pdf") ->withMime('application/pdf'), ]; }
private function buildPdf(): string { // Standalone document: the static factory wires the default // dependencies, so this example is self-contained. If you use the // nextpdf/laravel integration, resolve a document via its documented // binding instead — see the integration page linked below. $document = Document::createStandalone(); $document->addPage(); $document->cell(0, 10, "Invoice #{$this->invoice->number}", newLine: true);
return $document->getPdfData(); }}Gửi nó như bình thường:
use App\Mail\InvoiceMail;use Illuminate\Support\Facades\Mail;
Mail::to($invoice->customerEmail)->send(new InvoiceMail($invoice));Callback fromData() được gọi một cách lười biếng khi thông điệp được dựng, nên
PDF được tạo ở thời điểm gửi, không phải lúc khởi tạo. Đặt ->withMime('application/pdf')
để client của người nhận coi phần đó là một PDF thay vì đoán từ phần mở rộng. Bên
trong một controller, bạn có thể thay vào đó gọi $message->attachData($bytes, $name, ['mime' => 'application/pdf'])
trên một thông điệp thô, nhưng Attachment::fromData() trên Mailable là dạng hiện
đại đúng lề lối.
Symfony Mailer: đính kèm các byte vào một Email
Phần tiêu đề “Symfony Mailer: đính kèm các byte vào một Email”Email::attach() của Symfony Mailer chấp nhận body dưới dạng một chuỗi trong bộ
nhớ, kèm một tên tệp tường minh và content type. Một lần nữa, không có tệp tạm.
<?php
declare(strict_types=1);
namespace App\Mailer;
use NextPDF\Core\DocumentFactory;use Symfony\Component\Mailer\MailerInterface;use Symfony\Component\Mime\Email;
final class InvoiceMailer{ public function __construct( private readonly MailerInterface $mailer, private readonly DocumentFactory $documents, ) {}
public function sendInvoice(string $to, int $invoiceId): void { // Build a fresh document from the factory, which wires the default // dependencies for you. (Use Document::createStandalone() if you do // not have the factory injected.) $document = $this->documents->create(); $document->addPage(); $document->cell(0, 10, "Invoice #{$invoiceId}", newLine: true);
$email = (new Email()) ->from('billing@example.com') ->to($to) ->subject("Invoice #{$invoiceId}") ->text('Your invoice is attached.') ->attach( $document->getPdfData(), "invoice-{$invoiceId}.pdf", 'application/pdf', );
$this->mailer->send($email); }}attach(string $body, ?string $name, ?string $contentType) nhận các byte trực
tiếp. Truyền 'application/pdf' làm đối số thứ ba để phần đó được gắn kiểu
đúng. Nếu bạn muốn đính kèm từ một luồng, attachFromPath() tồn tại, nhưng với nội
dung được tạo, dạng attach() trong bộ nhớ tránh một lần khứ hồi không cần thiết
tới đĩa.
Làm điều đó từ một job được xếp hàng
Phần tiêu đề “Làm điều đó từ một job được xếp hàng”Dựng một PDF nhiều trang và gửi mail đều đủ chậm để bạn không nên làm chúng trên luồng request. Hãy xếp hàng công việc. Mẫu là dispatch một job (hoặc, trong Laravel, xếp hàng chính Mailable) và dựng PDF trên worker.
Dạng Laravel đơn giản nhất: làm cho Mailable là ShouldQueue. Vì
callback Attachment::fromData() chạy khi thông điệp được xếp hàng được dựng, PDF
được tạo trên worker, không phải lúc dispatch.
use Illuminate\Contracts\Queue\ShouldQueue;use Illuminate\Mail\Mailable;
final class InvoiceMail extends Mailable implements ShouldQueue{ // ... same envelope(), content(), attachments() as above ...}// Dispatched to the queue; the worker builds the PDF and sends the mail.Mail::to($invoice->customerEmail)->queue(new InvoiceMail($invoice));Với Symfony, dispatch một thông điệp Messenger mang các định danh (không phải các
byte), và để handler dựng PDF và gửi email trên worker. Truyền một id hóa đơn, tra
bản ghi trong handler, và gọi InvoiceMailer được trình bày ở trên. Mailer của
Symfony vốn đã bất đồng bộ khi một transport Messenger được cấu hình cho
SendEmailMessage, nên ngay cả một send() được gọi đồng bộ
cũng có thể được vận chuyển tới một worker.
Nếu bạn tạo PDF trong một job tạo chuyên biệt rồi gửi mail nó, xem
Tạo một PDF trong một job được xếp hàng
để biết bề mặt GeneratePdfJob / GeneratePdfMessage của tích hợp và các quy tắc
an-toàn-worker của nó. Một hình dạng phổ biến là: một job tạo và lưu PDF, một
job thứ hai (hoặc callback thành công) đọc nó lại và gửi mail nó. Khi bạn giữ
các byte trong bộ nhớ xuyên suốt một job duy nhất, bạn bỏ qua tệp hoàn toàn.
Ghi chú về kích thước, inline, và đính kèm
Phần tiêu đề “Ghi chú về kích thước, inline, và đính kèm”- Đính kèm, không phải inline. Một hóa đơn hoặc báo cáo được tạo gần như luôn
là một tệp tải về riêng biệt, nên hãy đính kèm nó. Hãy dành nội dung inline
(
Content-Disposition: inlinevới một tham chiếucid:) cho các ảnh bạn nhúng trong body HTML — một PDF không phải là nội dung body. - Để ý kích thước. Các tệp đính kèm email được mã hóa base64 khi truyền, điều này làm phình payload lên khoảng một phần ba. Nhiều máy chủ nhận giới hạn một thông điệp ở khoảng 10–25 MB sau khi mã hóa. Với một báo cáo lớn, hãy đính kèm một email thông báo ngắn kèm một liên kết tải về có chữ ký thay vì chính tệp đó, và phục vụ PDF qua HTTP — xem Trả về một PDF được tạo từ một controller.
- Dựng một lần, đính kèm một lần. Bắt các byte trả về một lần vào một biến và tái sử dụng chuỗi đó cho thông điệp. Đừng gọi phương thức đầu ra cuối cùng lặp đi lặp lại cho một email.
- Bộ nhớ trên worker. Giữ toàn bộ PDF trong bộ nhớ là ổn cho các hóa đơn và
biên lai điển hình. Với các tài liệu rất lớn trên một worker bị hạn chế, hãy lưu
vào một đường dẫn tạm với
save(), đính kèm qua Laravelattach($path)/ SymfonyEmail::attachFromPath($path), rồi xóa tệp — đánh đổi một lần khứ hồi đĩa lấy một đỉnh bộ nhớ thấp hơn. (Với lối trong-bộ-nhớ dùng ở mọi nơi khác trên trang này, dạng dựa-trên-byte là LaravelAttachment::fromData()/ SymfonyEmail::attach($bytes, 'name.pdf', 'application/pdf').)
Ghi chú bảo mật
Phần tiêu đề “Ghi chú bảo mật”- Đừng bao giờ nội suy đầu vào người dùng chưa xác thực vào tên tệp đính kèm. Hãy truyền một giá trị bạn kiểm soát (một số hóa đơn bạn đã tạo), để client của người nhận không thể bị lái bởi một tên được dàn dựng.
- Chỉ gửi cho mỗi khách hàng tài liệu của riêng họ. Hãy dựng PDF từ các bản ghi của chủ thể đã xác thực bên trong job, không phải từ một id được nhận một cách tin tưởng từ request.
- Trong một lối được xếp hàng, hãy ghi log lớp ngoại lệ và một id tương quan khi
thất bại, không bao giờ là thông điệp ngoại lệ hay một stack trace. Đừng bao giờ
ghi một khối
catchrỗng quanh việc dựng-và-gửi.
Xem thêm
Phần tiêu đề “Xem thêm”- Nhúng tệp và tạo PDF portfolio — chiều ngược lại: đính kèm tệp vào trong PDF.
- Tạo một PDF trong một job được xếp hàng — chuyển việc tạo ra khỏi luồng request.
- Trả về một PDF được tạo từ một controller — phục vụ PDF qua HTTP thay vì đính kèm nó.
- Sử dụng Laravel trong sản xuất — phân giải một tài liệu từ container và an toàn worker.
- Sử dụng Symfony trong sản xuất — các worker Messenger và document factory.