Ga naar inhoud
getnextpdf.com

Een gegenereerde PDF mailen als mailerbijlage

Een factuur, bon of rapport mailen is een van de meest voorkomende dingen die je doet met een gegenereerd PDF. De schone manier om het te doen is het document bouwen, de ruwe bytes ervan nemen, en die bytes rechtstreeks aan de bijlage-API van je mailer overhandigen. Je hebt op geen enkel moment een tijdelijk bestand op schijf nodig.

Dit is een how-to. Hij gaat ervan uit dat je al weet hoe je mail verstuurt in je framework. De NextPDF-kant is één aanroep: Document::getPdfData() retourneert de ruwe Portable Document Format (PDF)-bytes als een string. De bijlagekant behoort volledig aan je mailer toe — deze handleiding gebruikt Laravels Attachment::fromData() en de Email::attach() van Symfony Mailer.

NextPDF levert geen mailhelper mee. Er is geen “mail deze PDF”-methode op een document, en je zou wantrouwend moeten zijn tegen elk voorbeeld dat er een laat zien. De bijlage-API is altijd die van je framework.

Deze pagina is de uitgaande tegenhanger van bestanden insluiten in een PDF. Die handleiding voegt bestanden in de PDF toe als ingesloten streams; deze handleiding voegt het voltooide PDF aan een e-mail toe. Het zijn verschillende operaties — verwar de twee niet.

Wat je verder ook doet, de NextPDF-stap is hetzelfde: produceer de bytes.

<?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() bouwt het document en retourneert de bytes ervan als een string. Hij schrijft niets naar schijf en stuurt geen Hypertext Transfer Protocol (HTTP)-headers, wat precies is wat je wilt voor een bijlage.

Als je het document alleen via het type NextPDF\Contracts\PdfDocumentInterface vasthoudt (bijvoorbeeld een waarde die de framework-integratie je gaf), gebruik dan in plaats daarvan het equivalent op contractniveau:

use NextPDF\Contracts\OutputDestination;
$bytes = $document->output(dest: OutputDestination::String);

output(dest: OutputDestination::String) is gedeclareerd op PdfDocumentInterface en retourneert dezelfde ruwe bytes zonder enige headers uit te stoten. Gebruik getPdfData() wanneer je een concrete NextPDF\Core\Document vasthoudt, en de output(...)-vorm wanneer je alleen de interface hebt.

Als je de Laravel- of Symfony-integratie gebruikt, herleid dan een vers document uit de container in plaats van er rechtstreeks een te construeren — zie Een gegenereerd PDF retourneren vanuit een controller voor het herleidingspad in elk framework. Alles hieronder werkt hetzelfde, ongeacht hoe je het document hebt verkregen.

Laravel: in-memory bytes toevoegen aan een Mailable

Sectie met titel “Laravel: in-memory bytes toevoegen aan een Mailable”

Laravels Attachment::fromData() neemt een callback die de ruwe bytes retourneert, plus een bestandsnaam. Er is geen tijdelijk bestand. Implementeer attachments() op je Mailable en retourneer één Attachment.

app/Mail/InvoiceMail.php
<?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();
}
}

Verstuur het zoals gewoonlijk:

use App\Mail\InvoiceMail;
use Illuminate\Support\Facades\Mail;
Mail::to($invoice->customerEmail)->send(new InvoiceMail($invoice));

De fromData()-callback wordt lui aangeroepen wanneer het bericht wordt gebouwd, dus de PDF wordt gegenereerd op verzendtijd, niet bij constructie. Stel ->withMime('application/pdf') in zodat de client van de ontvanger het deel als een PDF behandelt in plaats van te gokken op basis van de extensie. Binnen een controller kun je in plaats daarvan $message->attachData($bytes, $name, ['mime' => 'application/pdf']) aanroepen op een ruw bericht, maar Attachment::fromData() op de Mailable is de idiomatische moderne vorm.

De Email::attach() van Symfony Mailer accepteert de body als een in-memory-string, met een expliciete bestandsnaam en content type. Wederom geen tijdelijk bestand.

src/Mailer/InvoiceMailer.php
<?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) neemt de bytes rechtstreeks. Geef 'application/pdf' als derde argument door zodat het deel correct getypeerd is. Als je liever vanuit een stream toevoegt, bestaat attachFromPath(), maar voor gegenereerde inhoud vermijdt de in-memory-attach()-vorm een nodeloze omweg naar schijf.

Een veelpagina’s-PDF bouwen en mail versturen zijn beide traag genoeg dat je ze niet op de request-thread zou moeten doen. Zet het werk in de queue. Het patroon is een job dispatchen (of, in Laravel, de Mailable zelf in de queue zetten) en de PDF op de worker bouwen.

De eenvoudigste Laravel-vorm: maak de Mailable ShouldQueue. Omdat de Attachment::fromData()-callback draait wanneer het queued bericht wordt gebouwd, wordt de PDF op de worker gegenereerd, niet bij dispatch.

app/Mail/InvoiceMail.php (queued)
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));

Voor Symfony dispatch je een Messenger-message die de identifiers draagt (niet de bytes), en laat je de handler de PDF bouwen en de e-mail op de worker versturen. Geef een factuur-id door, zoek het record op in de handler, en roep de hierboven getoonde InvoiceMailer aan. De mailer van Symfony is al asynchroon wanneer een Messenger-transport is geconfigureerd voor SendEmailMessage, dus zelfs een synchroon-aangeroepen send() kan naar een worker worden getransporteerd.

Als je de PDF in een toegewijde generatiejob genereert en hem daarna mailt, zie Een PDF genereren in een queued job voor het GeneratePdfJob / GeneratePdfMessage-oppervlak van de integratie en de worker-veiligheidsregels ervan. Een veelvoorkomende vorm is: één job genereert en bewaart de PDF, een tweede job (of de success-callback) leest hem terug en mailt hem. Wanneer je de bytes binnen één enkele job in het geheugen houdt, sla je het bestand volledig over.

  • Bijlage, niet inline. Een gegenereerde factuur of rapport is vrijwel altijd een apart downloadbaar bestand, dus voeg het als bijlage toe. Reserveer inline-inhoud (Content-Disposition: inline met een cid:-referentie) voor afbeeldingen die je in de HTML-body insluit — een PDF is geen body-inhoud.
  • Let op de grootte. E-mailbijlagen worden onderweg base64-gecodeerd, wat de payload met ongeveer een derde opblaast. Veel ontvangende servers begrenzen een bericht op ongeveer 10–25 MB na codering. Voeg voor een groot rapport een korte notificatie-e-mail met een ondertekende downloadlink toe in plaats van het bestand zelf, en serveer de PDF over HTTP — zie Een gegenereerd PDF retourneren vanuit een controller.
  • Bouw één keer, voeg één keer toe. Vang de geretourneerde bytes één keer op in een variabele en hergebruik die string voor het bericht. Roep de uiteindelijke uitvoermethode niet herhaaldelijk aan voor één e-mail.
  • Geheugen op de worker. De volledige PDF in het geheugen houden is prima voor typische facturen en bonnen. Voor zeer grote documenten op een beperkte worker bewaar je naar een tijdelijk pad met save(), voeg je toe via Laravel attach($path) / Symfony Email::attachFromPath($path), en verwijder je het bestand daarna — een schijf-omweg ingeruild voor een lagere geheugenpiek. (Voor het in-memory-pad dat overal elders op deze pagina wordt gebruikt is de byte-gebaseerde vorm Laravel Attachment::fromData() / Symfony Email::attach($bytes, 'name.pdf', 'application/pdf').)
  • Interpoleer nooit ongevalideerde gebruikersinvoer in de bijlagebestandsnaam. Geef een waarde door die je beheert (een factuurnummer dat je hebt gegenereerd), zodat de client van de ontvanger niet door een geprepareerde naam kan worden gestuurd.
  • Stuur elke klant alleen zijn eigen document. Bouw de PDF uit de records van het geauthenticeerde subject binnen de job, niet uit een id die op goed vertrouwen uit de request is genomen.
  • Log in een queued pad de uitzonderingsklasse en een correlatie-id bij falen, nooit het uitzonderingsbericht of een stack trace. Schrijf nooit een leeg catch-blok rond het bouwen-en-versturen.