Ein generiertes PDF als Mailer-Anhang per E-Mail versenden
Auf einen Blick
Abschnitt betitelt „Auf einen Blick“Eine Rechnung, eine Quittung oder einen Bericht per E-Mail zu versenden ist eines der häufigsten Dinge, die Sie mit einem generierten PDF tun. Der saubere Weg dazu ist, das Dokument zu bauen, seine rohen Bytes zu nehmen und diese Bytes direkt an die Anhang-API Ihres Mailers zu übergeben. Sie brauchen zu keinem Zeitpunkt eine temporäre Datei auf der Festplatte.
Dies ist eine betriebliche Anleitung. Sie setzt voraus, dass Sie bereits wissen,
wie man in Ihrem Framework Mail versendet. Die NextPDF-Seite ist ein einzelner
Aufruf: Document::getPdfData() gibt die rohen Portable-Document-Format-(PDF-)Bytes
als Zeichenkette zurück. Die Anhang-Seite gehört vollständig Ihrem Mailer — diese
Anleitung verwendet Laravels Attachment::fromData() und Symfony Mailers
Email::attach().
NextPDF liefert keinen Mail-Helfer aus. Es gibt keine „diese PDF per E-Mail“-Methode auf einem Dokument, und Sie sollten jedem Beispiel misstrauen, das eine zeigt. Die Anhang-API gehört immer Ihrem Framework.
Diese Seite ist das ausgehende Gegenstück zum Einbetten von Dateien in ein PDF. Jene Anleitung hängt Dateien in das PDF als eingebettete Streams an; diese Anleitung hängt das fertige PDF an eine E-Mail an. Das sind unterschiedliche Operationen — verwechseln Sie die beiden nicht.
Holen Sie die rohen PDF-Bytes
Abschnitt betitelt „Holen Sie die rohen PDF-Bytes“Was auch immer Sie sonst tun, der NextPDF-Schritt ist derselbe: die Bytes erzeugen.
<?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() baut das Dokument und gibt seine Bytes als Zeichenkette zurück. Es
schreibt nichts auf die Festplatte und sendet keine
Hypertext-Transfer-Protocol-(HTTP-)Header, was genau das ist, was Sie für einen
Anhang wollen.
Wenn Sie das Dokument nur über den Typ
NextPDF\Contracts\PdfDocumentInterface halten (zum Beispiel einen Wert, den Ihnen
die Framework-Integration ausgehändigt hat), verwenden Sie stattdessen das
Pendant auf Contract-Ebene:
use NextPDF\Contracts\OutputDestination;
$bytes = $document->output(dest: OutputDestination::String);output(dest: OutputDestination::String) ist auf PdfDocumentInterface deklariert
und gibt dieselben rohen Bytes zurück, ohne irgendwelche Header zu emittieren.
Verwenden Sie getPdfData(), wenn Sie ein konkretes NextPDF\Core\Document halten,
und die output(...)-Form, wenn Sie nur das Interface haben.
Wenn Sie die Laravel- oder Symfony-Integration verwenden, lösen Sie ein frisches Dokument aus dem Container auf, statt eines direkt zu konstruieren — siehe Ein generiertes PDF aus einem Controller zurückgeben für den Auflösungspfad in jedem Framework. Alles unten funktioniert gleich, unabhängig davon, wie Sie das Dokument erhalten haben.
Laravel: In-Memory-Bytes an ein Mailable anhängen
Abschnitt betitelt „Laravel: In-Memory-Bytes an ein Mailable anhängen“Laravels Attachment::fromData() nimmt einen Callback, der die rohen Bytes
zurückgibt, plus einen Dateinamen. Es gibt keine temporäre Datei. Implementieren Sie
attachments() auf Ihrem Mailable und geben Sie ein Attachment zurück.
<?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(); }}Versenden Sie es wie gewohnt:
use App\Mail\InvoiceMail;use Illuminate\Support\Facades\Mail;
Mail::to($invoice->customerEmail)->send(new InvoiceMail($invoice));Der fromData()-Callback wird verzögert aufgerufen, wenn die Nachricht gebaut wird,
sodass das PDF zum Sendezeitpunkt erzeugt wird, nicht bei der Konstruktion. Setzen
Sie ->withMime('application/pdf'), sodass der Client des Empfängers den Part als
PDF behandelt, statt aus der Erweiterung zu raten. Innerhalb eines Controllers können
Sie stattdessen $message->attachData($bytes, $name, ['mime' => 'application/pdf'])
auf einer rohen Nachricht aufrufen, aber Attachment::fromData() auf dem Mailable
ist die idiomatische moderne Form.
Symfony Mailer: Bytes an ein Email anhängen
Abschnitt betitelt „Symfony Mailer: Bytes an ein Email anhängen“Symfony Mailers Email::attach() akzeptiert den Body als In-Memory-Zeichenkette,
mit einem expliziten Dateinamen und Content-Type. Auch hier keine temporäre Datei.
<?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) nimmt die Bytes direkt.
Übergeben Sie 'application/pdf' als drittes Argument, sodass der Part korrekt
typisiert ist. Wenn Sie lieber aus einem Stream anhängen, existiert
attachFromPath(), aber für generierten Inhalt vermeidet die In-Memory-attach()-Form
einen unnötigen Umweg über die Festplatte.
Tun Sie es aus einem Queued Job
Abschnitt betitelt „Tun Sie es aus einem Queued Job“Ein mehrseitiges PDF zu bauen und Mail zu versenden sind beide langsam genug, dass Sie es nicht auf dem Request-Thread tun sollten. Stellen Sie die Arbeit in die Queue. Das Muster ist, einen Job zu dispatchen (oder, in Laravel, das Mailable selbst in die Queue zu stellen) und das PDF auf dem Worker zu bauen.
Die einfachste Laravel-Form: Machen Sie das Mailable ShouldQueue. Weil der
Attachment::fromData()-Callback läuft, wenn die in die Queue gestellte Nachricht
gebaut wird, wird das PDF auf dem Worker erzeugt, nicht beim 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));Für Symfony dispatchen Sie eine Messenger-Nachricht, die die Bezeichner trägt (nicht
die Bytes), und lassen den Handler das PDF bauen und die E-Mail auf dem Worker
versenden. Übergeben Sie eine Invoice-Id, schlagen Sie den Datensatz im Handler nach
und rufen Sie den oben gezeigten InvoiceMailer auf. Symfonys Mailer ist bereits
asynchron, wenn ein Messenger-Transport für SendEmailMessage konfiguriert ist,
sodass selbst ein synchron aufgerufenes send() zu einem Worker transportiert werden
kann.
Wenn Sie das PDF in einem dedizierten Erzeugungs-Job erzeugen und es dann
versenden, siehe
Ein PDF in einem Queued Job erzeugen
für die GeneratePdfJob- / GeneratePdfMessage-Oberfläche der Integration und ihre
Worker-Sicherheitsregeln. Eine übliche Form ist: ein Job erzeugt und speichert das
PDF, ein zweiter Job (oder der Success-Callback) liest es zurück und versendet es.
Wenn Sie die Bytes über einen einzelnen Job hinweg im Speicher halten, überspringen
Sie die Datei vollständig.
Hinweise zu Größe, Inline und Anhang
Abschnitt betitelt „Hinweise zu Größe, Inline und Anhang“- Anhang, nicht Inline. Eine generierte Rechnung oder ein Bericht ist fast immer
eine separate, herunterladbare Datei, also hängen Sie sie an. Reservieren Sie
Inline-Inhalt (
Content-Disposition: inlinemit einercid:-Referenz) für Bilder, die Sie in den HTML-Body einbetten — ein PDF ist kein Body-Inhalt. - Achten Sie auf die Größe. E-Mail-Anhänge werden im Transit base64-codiert, was die Payload um etwa ein Drittel aufbläht. Viele empfangende Server begrenzen eine Nachricht auf rund 10–25 MB nach der Codierung. Für einen großen Bericht hängen Sie eine kurze Benachrichtigungs-E-Mail mit einem signierten Download-Link statt der Datei selbst an und liefern das PDF über HTTP aus — siehe Ein generiertes PDF aus einem Controller zurückgeben.
- Einmal bauen, einmal anhängen. Fangen Sie die zurückgegebenen Bytes einmal in einer Variablen ein und verwenden Sie diese Zeichenkette für die Nachricht wieder. Rufen Sie die finale Ausgabemethode nicht wiederholt für eine E-Mail auf.
- Speicher auf dem Worker. Das vollständige PDF im Speicher zu halten ist für
typische Rechnungen und Quittungen in Ordnung. Für sehr große Dokumente auf einem
eingeschränkten Worker speichern Sie es mit
save()in einen temporären Pfad, hängen es über Laravelattach($path)/ SymfonyEmail::attachFromPath($path)an und löschen dann die Datei — wobei Sie einen Festplattenumweg gegen einen niedrigeren Speicher-Peak tauschen. (Für den In-Memory-Pfad, der überall sonst auf dieser Seite verwendet wird, ist die byte-basierte Form LaravelAttachment::fromData()/ SymfonyEmail::attach($bytes, 'name.pdf', 'application/pdf').)
Sicherheitshinweise
Abschnitt betitelt „Sicherheitshinweise“- Interpolieren Sie niemals unvalidierte Nutzereingaben in den Anhang-Dateinamen. Übergeben Sie einen Wert, den Sie kontrollieren (eine Rechnungsnummer, die Sie generiert haben), sodass der Client des Empfängers nicht durch einen manipulierten Namen gesteuert werden kann.
- Versenden Sie jedem Kunden nur sein eigenes Dokument. Bauen Sie das PDF aus den Datensätzen des authentifizierten Subjekts innerhalb des Jobs, nicht aus einer Id, die auf gut Glauben aus der Anfrage genommen wird.
- Loggen Sie in einem Queued-Pfad bei einem Fehler die Exception-Klasse und eine
Korrelations-Id, niemals die Exception-Nachricht oder einen Stack-Trace. Schreiben
Sie niemals einen leeren
catch-Block um das Bauen-und-Versenden.
Siehe auch
Abschnitt betitelt „Siehe auch“- Dateien einbetten und PDF-Portfolios erstellen — das Umgekehrte: Dateien in das PDF anhängen.
- Ein PDF in einem Queued Job erzeugen — verschieben Sie die Erzeugung vom Request-Thread.
- Ein generiertes PDF aus einem Controller zurückgeben — liefern Sie das PDF über HTTP aus, statt es anzuhängen.
- Laravel-Produktionsnutzung — ein Dokument aus dem Container auflösen und Worker-Sicherheit.
- Symfony-Produktionsnutzung — Messenger-Worker und die Document-Factory.