Wysyłanie wygenerowanego PDF jako załącznika mailera
W skrócie
Dział zatytułowany „W skrócie”Wysłanie faktury, paragonu czy raportu to jedna z najczęstszych rzeczy, które robisz z wygenerowanym PDF-em. Czystym sposobem na to jest zbudowanie dokumentu, wzięcie jego surowych bajtów i przekazanie tych bajtów wprost do API załączników twojego mailera. Nie potrzebujesz pliku tymczasowego na dysku w żadnym momencie.
To poradnik how-to. Zakłada, że potrafisz już wysyłać pocztę w swoim frameworku.
Strona NextPDF to pojedyncze wywołanie: Document::getPdfData() zwraca surowe
bajty Portable Document Format (PDF) jako łańcuch znaków. Strona załącznika należy
w całości do twojego mailera — ten przewodnik używa Laravel
Attachment::fromData() oraz Symfony Mailer Email::attach().
NextPDF nie dostarcza pomocnika pocztowego. Nie ma metody „wyślij ten PDF mailem” na dokumencie i powinieneś być podejrzliwy wobec każdego przykładu, który taką pokazuje. API załącznika należy zawsze do twojego frameworka.
Ta strona jest wychodzącym odpowiednikiem osadzania plików wewnątrz PDF. Tamten przewodnik dołącza pliki do PDF jako osadzone strumienie; ten przewodnik dołącza gotowy PDF do e-maila. To różne operacje — nie myl ich.
Pobierz surowe bajty PDF
Dział zatytułowany „Pobierz surowe bajty PDF”Cokolwiek innego robisz, krok NextPDF jest ten sam: wytwórz bajty.
<?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() buduje dokument i zwraca jego bajty jako łańcuch znaków. Nie
zapisuje nic na dysk i nie wysyła żadnych nagłówków Hypertext Transfer Protocol
(HTTP), co jest dokładnie tym, czego chcesz dla załącznika.
Jeśli trzymasz dokument tylko przez typ
NextPDF\Contracts\PdfDocumentInterface (na przykład wartość, którą przekazała ci
integracja z frameworkiem), użyj zamiast tego odpowiednika na poziomie kontraktu:
use NextPDF\Contracts\OutputDestination;
$bytes = $document->output(dest: OutputDestination::String);output(dest: OutputDestination::String) jest zadeklarowane na
PdfDocumentInterface i zwraca te same surowe bajty bez emitowania żadnych
nagłówków. Użyj getPdfData(), gdy trzymasz konkretny NextPDF\Core\Document, a
formy output(...), gdy masz tylko interfejs.
Jeśli używasz integracji Laravel lub Symfony, rozwiąż świeży dokument z kontenera, zamiast konstruować go bezpośrednio — zobacz Zwracanie wygenerowanego PDF z kontrolera po ścieżkę rozwiązywania w każdym frameworku. Wszystko poniżej działa tak samo niezależnie od tego, jak uzyskałeś dokument.
Laravel: dołączanie bajtów z pamięci do Mailable
Dział zatytułowany „Laravel: dołączanie bajtów z pamięci do Mailable”Laravel Attachment::fromData() przyjmuje wywołanie zwrotne, które zwraca surowe
bajty, plus nazwę pliku. Nie ma pliku tymczasowego. Zaimplementuj attachments()
na swoim Mailable i zwróć jeden 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(); }}Wyślij to jak zwykle:
use App\Mail\InvoiceMail;use Illuminate\Support\Facades\Mail;
Mail::to($invoice->customerEmail)->send(new InvoiceMail($invoice));Wywołanie zwrotne fromData() jest wykonywane leniwie, gdy wiadomość jest
budowana, więc PDF jest generowany w czasie wysyłki, a nie konstrukcji. Ustaw
->withMime('application/pdf'), tak by klient odbiorcy traktował część jako PDF,
a nie zgadywał z rozszerzenia. Wewnątrz kontrolera możesz zamiast tego wywołać
$message->attachData($bytes, $name, ['mime' => 'application/pdf']) na surowej
wiadomości, ale Attachment::fromData() na Mailable to idiomatyczna współczesna
forma.
Symfony Mailer: dołączanie bajtów do Email
Dział zatytułowany „Symfony Mailer: dołączanie bajtów do Email”Symfony Mailer Email::attach() przyjmuje treść jako łańcuch znaków w pamięci, z
jawną nazwą pliku i typem zawartości. Znów żadnego pliku tymczasowego.
<?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) przyjmuje bajty
bezpośrednio. Przekaż 'application/pdf' jako trzeci argument, tak by część była
otypowana poprawnie. Jeśli wolisz dołączać ze strumienia, attachFromPath()
istnieje, ale dla wygenerowanej treści forma attach() w pamięci unika zbędnego
obiegu na dysk.
Zrób to z zadania w kolejce
Dział zatytułowany „Zrób to z zadania w kolejce”Budowanie wielostronicowego PDF i wysyłanie poczty są oba na tyle powolne, że nie powinieneś robić ich na wątku żądania. Skolejkuj pracę. Wzorzec polega na wysłaniu zadania (lub, w Laravel, skolejkowaniu samego Mailable) i zbudowaniu PDF na workerze.
Najprostsza forma Laravel: uczyń Mailable ShouldQueue. Ponieważ wywołanie
zwrotne Attachment::fromData() uruchamia się, gdy skolejkowana wiadomość jest
budowana, PDF jest generowany na workerze, a nie przy wysyłce.
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));Dla Symfony wyślij wiadomość Messenger niosącą identyfikatory (nie bajty) i
pozwól, by handler zbudował PDF i wysłał e-mail na workerze. Przekaż id faktury,
odszukaj rekord w handlerze i wywołaj pokazany powyżej InvoiceMailer. Mailer
Symfony jest już asynchroniczny, gdy transport Messenger jest skonfigurowany dla
SendEmailMessage, więc nawet synchronicznie wywołane send() może zostać
przetransportowane na workera.
Jeśli generujesz PDF w dedykowanym zadaniu generowania, a potem wysyłasz go
mailem, zobacz
Generowanie PDF w zadaniu w kolejce
po powierzchnię GeneratePdfJob / GeneratePdfMessage integracji oraz jej reguły
bezpieczeństwa workera. Częsty kształt to: jedno zadanie generuje i zapisuje PDF,
drugie zadanie (lub wywołanie zwrotne sukcesu) odczytuje go z powrotem i wysyła
mailem. Gdy trzymasz bajty w pamięci w obrębie jednego zadania, całkowicie
pomijasz plik.
Uwagi o rozmiarze, inline i załącznikach
Dział zatytułowany „Uwagi o rozmiarze, inline i załącznikach”- Załącznik, nie inline. Wygenerowana faktura lub raport to niemal zawsze
osobny plik do pobrania, więc dołącz go jako załącznik. Rezerwuj treść inline
(
Content-Disposition: inlinez odwołaniemcid:) dla obrazów, które osadzasz w treści HTML — PDF nie jest treścią ciała wiadomości. - Pilnuj rozmiaru. Załączniki e-mail są w tranzycie kodowane w base64, co zwiększa ładunek o około jedną trzecią. Wiele serwerów odbierających ogranicza wiadomość do około 10–25 MB po zakodowaniu. Dla dużego raportu dołącz krótki e-mail powiadamiający z podpisanym linkiem do pobrania zamiast samego pliku i serwuj PDF przez HTTP — zobacz Zwracanie wygenerowanego PDF z kontrolera.
- Buduj raz, dołączaj raz. Przechwyć zwrócone bajty raz do zmiennej i wykorzystaj ten łańcuch znaków ponownie dla wiadomości. Nie wywołuj końcowej metody wyjściowej wielokrotnie dla jednego e-maila.
- Pamięć na workerze. Trzymanie pełnego PDF w pamięci jest w porządku dla
typowych faktur i paragonów. Dla bardzo dużych dokumentów na ograniczonym
workerze zapisz do ścieżki tymczasowej za pomocą
save(), dołącz przez Laravelattach($path)/ SymfonyEmail::attachFromPath($path), a następnie usuń plik — wymieniając obieg na dysk na niższy szczyt pamięci. (Dla ścieżki w pamięci używanej wszędzie indziej na tej stronie formą opartą na bajtach jest LaravelAttachment::fromData()/ SymfonyEmail::attach($bytes, 'name.pdf', 'application/pdf').)
Uwagi dotyczące bezpieczeństwa
Dział zatytułowany „Uwagi dotyczące bezpieczeństwa”- Nigdy nie interpoluj niezweryfikowanego wejścia użytkownika w nazwę pliku załącznika. Przekaż wartość, którą kontrolujesz (numer faktury, który wygenerowałeś), tak by klient odbiorcy nie mógł zostać sterowany spreparowaną nazwą.
- Wysyłaj każdemu klientowi tylko jego własny dokument. Buduj PDF z rekordów uwierzytelnionego podmiotu wewnątrz zadania, a nie z id wziętego na wiarę z żądania.
- Na ścieżce w kolejce loguj klasę wyjątku i id korelacji przy awarii, nigdy
wiadomości wyjątku ani śladu stosu. Nigdy nie pisz pustego bloku
catchwokół budowy i wysyłki.
Zobacz też
Dział zatytułowany „Zobacz też”- Osadzanie plików i tworzenie portfolio PDF — odwrotność: dołącz pliki do PDF.
- Generowanie PDF w zadaniu w kolejce — przenieś generowanie poza wątek żądania.
- Zwracanie wygenerowanego PDF z kontrolera — serwuj PDF przez HTTP zamiast go dołączać.
- Produkcyjne użycie Laravel — rozwiązywanie dokumentu z kontenera i bezpieczeństwo workera.
- Produkcyjne użycie Symfony — workery Messenger i fabryka dokumentów.