Inviare via email un PDF generato come allegato del mailer
In sintesi
Sezione intitolata “In sintesi”Inviare via email una fattura, una ricevuta o un report è una delle cose più comuni che si fanno con un PDF generato. Il modo pulito di farlo è costruire il documento, prenderne i byte grezzi e consegnare quei byte direttamente all’API di allegato del proprio mailer. Non serve un file temporaneo su disco in alcun momento.
Questa è una guida pratica. Presuppone che si sappia già inviare email nel proprio
framework. Il lato NextPDF è una sola chiamata: Document::getPdfData()
restituisce i byte grezzi Portable Document Format (PDF) come stringa. Il lato
allegato appartiene interamente al proprio mailer — questa guida usa
Attachment::fromData() di Laravel ed Email::attach() di Symfony Mailer.
NextPDF non include un helper di posta. Non c’è alcun metodo «invia questo PDF via email» su un documento, e si dovrebbe diffidare di qualsiasi esempio che ne mostri uno. L’API di allegato è sempre quella del proprio framework.
Questa pagina è la controparte in uscita di incorporare file all’interno di un PDF. Quella guida allega file dentro il PDF come stream incorporati; questa guida allega il PDF finito a un’email. Sono operazioni diverse — non confondere le due.
Ottenere i byte grezzi del PDF
Sezione intitolata “Ottenere i byte grezzi del PDF”Qualunque altra cosa si faccia, il passaggio NextPDF è lo stesso: produrre i 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() costruisce il documento e ne restituisce i byte come stringa. Non
scrive nulla su disco e non invia alcun header Hypertext Transfer Protocol (HTTP),
che è esattamente ciò che si vuole per un allegato.
Se si detiene il documento solo tramite il tipo
NextPDF\Contracts\PdfDocumentInterface (per esempio, un valore che
l’integrazione del framework vi ha consegnato), usare invece l’equivalente a
livello di contratto:
use NextPDF\Contracts\OutputDestination;
$bytes = $document->output(dest: OutputDestination::String);output(dest: OutputDestination::String) è dichiarato su
PdfDocumentInterface e restituisce gli stessi byte grezzi senza emettere alcun
header. Usare getPdfData() quando si detiene un NextPDF\Core\Document concreto,
e la forma output(...) quando si ha solo l’interfaccia.
Se si usa l’integrazione Laravel o Symfony, risolvere un documento nuovo dal container anziché costruirne uno direttamente — vedere Restituire un PDF generato da un controller per il percorso di risoluzione in ciascun framework. Tutto ciò che segue funziona allo stesso modo indipendentemente da come si è ottenuto il documento.
Laravel: allegare byte in memoria a un Mailable
Sezione intitolata “Laravel: allegare byte in memoria a un Mailable”Attachment::fromData() di Laravel accetta una callback che restituisce i byte
grezzi, più un nome file. Non c’è alcun file temporaneo. Implementare
attachments() sul proprio Mailable e restituire un 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(); }}Inviarla come al solito:
use App\Mail\InvoiceMail;use Illuminate\Support\Facades\Mail;
Mail::to($invoice->customerEmail)->send(new InvoiceMail($invoice));La callback fromData() è invocata in modo lazy quando il messaggio viene
costruito, quindi il PDF è generato al momento dell’invio, non alla costruzione.
Impostare ->withMime('application/pdf') così che il client del destinatario
tratti la parte come un PDF anziché indovinare dall’estensione. All’interno di un
controller si può invece chiamare $message->attachData($bytes, $name, ['mime' => 'application/pdf'])
su un messaggio grezzo, ma Attachment::fromData() sul Mailable è la forma
moderna idiomatica.
Symfony Mailer: allegare byte a un Email
Sezione intitolata “Symfony Mailer: allegare byte a un Email”Email::attach() di Symfony Mailer accetta il corpo come stringa in memoria, con
un nome file e un content type espliciti. Di nuovo, nessun file temporaneo.
<?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) accetta i byte
direttamente. Passare 'application/pdf' come terzo argomento così che la parte
sia tipizzata correttamente. Se si preferisce allegare da uno stream esiste
attachFromPath(), ma per il contenuto generato la forma in memoria attach()
evita un inutile passaggio dal disco.
Farlo da un job in coda
Sezione intitolata “Farlo da un job in coda”Costruire un PDF di più pagine e inviare posta sono entrambi abbastanza lenti da non doverli fare sul thread della richiesta. Mettere il lavoro in coda. Il pattern consiste nel dispatchare un job (o, in Laravel, mettere in coda il Mailable stesso) e costruire il PDF sul worker.
La forma Laravel più semplice: rendere il Mailable ShouldQueue. Poiché la
callback Attachment::fromData() viene eseguita quando il messaggio in coda viene
costruito, il PDF è generato sul worker, non al 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));Per Symfony, dispatchare un messaggio Messenger che porta gli identificatori (non i
byte), e lasciare che l’handler costruisca il PDF e invii l’email sul worker.
Passare un id di fattura, cercare il record nell’handler e chiamare l’InvoiceMailer
mostrato sopra. Il mailer di Symfony è già asincrono quando un transport Messenger
è configurato per SendEmailMessage, quindi anche un send() chiamato in modo
sincrono può essere trasportato a un worker.
Se si genera il PDF in un job di generazione dedicato e poi lo si invia per email,
vedere
Generare un PDF in un job in coda
per la superficie GeneratePdfJob / GeneratePdfMessage dell’integrazione e le
sue regole di sicurezza del worker. Una forma comune è: un job genera e salva il
PDF, un secondo job (o la callback di successo) lo rilegge e lo invia per email.
Quando si tengono i byte in memoria attraverso un singolo job, si salta del tutto
il file.
Note su dimensione, inline e allegato
Sezione intitolata “Note su dimensione, inline e allegato”- Allegato, non inline. Una fattura o un report generato è quasi sempre un file
scaricabile separato, quindi allegarlo. Riservare il contenuto inline
(
Content-Disposition: inlinecon un riferimentocid:) per le immagini che si incorporano nel corpo HTML — un PDF non è contenuto del corpo. - Attenzione alla dimensione. Gli allegati email sono codificati in base64 durante il transito, il che gonfia il payload di circa un terzo. Molti server riceventi limitano un messaggio a circa 10–25 MB dopo la codifica. Per un report di grandi dimensioni, allegare una breve email di notifica con un link di download firmato anziché il file stesso, e servire il PDF su HTTP — vedere Restituire un PDF generato da un controller.
- Costruire una volta, allegare una volta. Catturare i byte restituiti una volta in una variabile e riutilizzare quella stringa per il messaggio. Non chiamare ripetutamente il metodo di output finale per una sola email.
- Memoria sul worker. Tenere l’intero PDF in memoria va bene per fatture e
ricevute tipiche. Per documenti molto grandi su un worker con risorse limitate,
salvare su un percorso temporaneo con
save(), allegare tramiteattach($path)di Laravel /Email::attachFromPath($path)di Symfony, poi eliminare il file — scambiando un passaggio dal disco per un picco di memoria più basso. (Per il percorso in memoria usato ovunque nel resto di questa pagina, la forma basata sui byte èAttachment::fromData()di Laravel /Email::attach($bytes, 'name.pdf', 'application/pdf')di Symfony.)
Note di sicurezza
Sezione intitolata “Note di sicurezza”- Non interpolare mai input utente non validato nel nome file dell’allegato. Passare un valore che si controlla (un numero di fattura che si è generato), così che il client del destinatario non possa essere pilotato da un nome appositamente creato.
- Inviare a ogni cliente solo il proprio documento. Costruire il PDF dai record del soggetto autenticato all’interno del job, non da un id preso per buono dalla richiesta.
- In un percorso in coda, registrare la classe dell’eccezione e un id di
correlazione in caso di fallimento, mai il messaggio dell’eccezione o uno stack
trace. Non scrivere mai un blocco
catchvuoto attorno al build-and-send.
Vedere anche
Sezione intitolata “Vedere anche”- Incorporare file e creare portfolio PDF — l’inverso: allegare file dentro il PDF.
- Generare un PDF in un job in coda — spostare la generazione fuori dal thread della richiesta.
- Restituire un PDF generato da un controller — servire il PDF su HTTP anziché allegarlo.
- Uso in produzione con Laravel — risolvere un documento dal container e sicurezza del worker.
- Uso in produzione con Symfony — i worker Messenger e il document factory.