Lewati ke konten
getnextpdf.com

Kirim PDF yang dihasilkan via email sebagai lampiran mailer

Mengirim faktur, kuitansi, atau laporan via email adalah salah satu hal paling umum yang Anda lakukan dengan PDF yang dihasilkan. Cara bersih melakukannya adalah dengan membangun dokumen, mengambil byte mentahnya, dan menyerahkan byte itu langsung ke API lampiran mailer Anda. Anda tidak memerlukan file sementara di disk pada titik mana pun.

Ini adalah sebuah how-to. Ia mengasumsikan Anda sudah tahu cara mengirim mail di framework Anda. Sisi NextPDF-nya adalah satu pemanggilan: Document::getPdfData() mengembalikan byte Portable Document Format (PDF) mentah sebagai string. Sisi lampiran sepenuhnya menjadi milik mailer Anda — panduan ini menggunakan Attachment::fromData() Laravel dan Email::attach() Symfony Mailer.

NextPDF tidak mengirim helper mail. Tidak ada metode “email this PDF” pada sebuah dokumen, dan Anda sebaiknya curiga terhadap contoh apa pun yang menunjukkan satu. API lampiran selalu milik framework Anda.

Halaman ini adalah pasangan keluar dari menanamkan file di dalam PDF. Panduan itu melampirkan file ke dalam PDF sebagai stream yang ditanamkan; panduan ini melampirkan PDF jadi ke sebuah email. Keduanya operasi berbeda — jangan mengacaukan keduanya.

Apa pun lagi yang Anda lakukan, langkah NextPDF-nya sama: hasilkan byte-nya.

<?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() membangun dokumen dan mengembalikan byte-nya sebagai string. Ia tidak menulis apa pun ke disk dan tidak mengirim header Hypertext Transfer Protocol (HTTP) apa pun, yang persis seperti yang Anda inginkan untuk sebuah lampiran.

Jika Anda memegang dokumen hanya melalui tipe NextPDF\Contracts\PdfDocumentInterface (misalnya, sebuah nilai yang diserahkan integrasi framework kepada Anda), gunakan padanan tingkat-kontraknya sebagai gantinya:

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

output(dest: OutputDestination::String) dideklarasikan pada PdfDocumentInterface dan mengembalikan byte mentah yang sama tanpa memancarkan header apa pun. Gunakan getPdfData() ketika Anda memegang NextPDF\Core\Document konkret, dan bentuk output(...) ketika Anda hanya memiliki interface-nya.

Jika Anda menggunakan integrasi Laravel atau Symfony, resolusi sebuah dokumen baru dari container alih-alih membangunnya secara langsung — lihat Kembalikan PDF yang dihasilkan dari sebuah controller untuk jalur resolusi di setiap framework. Segala hal di bawah bekerja sama terlepas dari bagaimana Anda memperoleh dokumennya.

Laravel: lampirkan byte in-memory ke sebuah Mailable

Bagian berjudul “Laravel: lampirkan byte in-memory ke sebuah Mailable”

Attachment::fromData() Laravel menerima sebuah callback yang mengembalikan byte mentah, ditambah sebuah nama file. Tidak ada file sementara. Implementasikan attachments() pada Mailable Anda dan kembalikan satu 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();
}
}

Kirim seperti biasa:

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

Callback fromData() dipanggil secara lazy saat pesannya dibangun, sehingga PDF dihasilkan pada waktu kirim, bukan saat konstruksi. Atur ->withMime('application/pdf') agar client penerima memperlakukan bagian itu sebagai PDF alih-alih menebak dari ekstensi. Di dalam sebuah controller Anda dapat sebagai gantinya memanggil $message->attachData($bytes, $name, ['mime' => 'application/pdf']) pada sebuah pesan mentah, tetapi Attachment::fromData() pada Mailable adalah bentuk modern yang idiomatik.

Symfony Mailer: lampirkan byte ke sebuah Email

Bagian berjudul “Symfony Mailer: lampirkan byte ke sebuah Email”

Email::attach() Symfony Mailer menerima body sebagai string in-memory, dengan nama file dan content type yang eksplisit. Lagi-lagi, tanpa file sementara.

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) menerima byte-nya secara langsung. Berikan 'application/pdf' sebagai argumen ketiga agar bagiannya bertipe dengan benar. Jika Anda lebih suka melampirkan dari sebuah stream, attachFromPath() ada, tetapi untuk konten yang dihasilkan bentuk in-memory attach() menghindari putaran ke disk yang tidak perlu.

Membangun PDF multi-halaman dan mengirim mail keduanya cukup lambat sehingga Anda sebaiknya tidak melakukannya di thread permintaan. Queue pekerjaannya. Polanya adalah mendispatch sebuah job (atau, di Laravel, men-queue Mailable itu sendiri) dan membangun PDF di worker.

Bentuk Laravel paling sederhana: jadikan Mailable ShouldQueue. Karena callback Attachment::fromData() berjalan saat pesan yang di-queue dibangun, PDF dihasilkan di worker, bukan saat 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));

Untuk Symfony, dispatch sebuah pesan Messenger yang membawa pengidentifikasi (bukan byte-nya), dan biarkan handler membangun PDF dan mengirim email di worker. Berikan sebuah id faktur, cari record-nya di handler, dan panggil InvoiceMailer yang ditunjukkan di atas. Mailer Symfony sudah asinkron ketika sebuah transport Messenger dikonfigurasi untuk SendEmailMessage, sehingga bahkan send() yang dipanggil secara sinkron dapat ditransportasikan ke sebuah worker.

Jika Anda menghasilkan PDF dalam sebuah job generasi khusus lalu mengirimnya via email, lihat Hasilkan PDF dalam sebuah job yang di-queue untuk permukaan GeneratePdfJob / GeneratePdfMessage milik integrasi dan aturan keamanan-worker-nya. Bentuk yang umum adalah: satu job menghasilkan dan menyimpan PDF, sebuah job kedua (atau callback sukses) membacanya kembali dan mengirimnya via email. Ketika Anda menjaga byte-nya di memori sepanjang satu job, Anda melewatkan file-nya sepenuhnya.

  • Lampiran, bukan inline. Sebuah faktur atau laporan yang dihasilkan hampir selalu merupakan file terpisah yang dapat diunduh, jadi lampirkan. Cadangkan konten inline (Content-Disposition: inline dengan referensi cid:) untuk gambar yang Anda tanamkan di body HTML — sebuah PDF bukan konten body.
  • Perhatikan ukurannya. Lampiran email di-encode-base64 saat transit, yang membengkakkan payload kira-kira sepertiga. Banyak server penerima membatasi sebuah pesan pada sekitar 10–25 MB setelah encoding. Untuk laporan besar, lampirkan email notifikasi singkat dengan tautan unduhan bertanda tangan alih-alih file itu sendiri, dan layani PDF melalui HTTP — lihat Kembalikan PDF yang dihasilkan dari sebuah controller.
  • Bangun sekali, lampirkan sekali. Tangkap byte yang dikembalikan sekali ke dalam sebuah variabel dan gunakan ulang string itu untuk pesannya. Jangan memanggil metode keluaran final berulang kali untuk satu email.
  • Memori di worker. Memegang seluruh PDF di memori baik-baik saja untuk faktur dan kuitansi tipikal. Untuk dokumen yang sangat besar pada worker yang terbatas, simpan ke path sementara dengan save(), lampirkan via Laravel attach($path) / Symfony Email::attachFromPath($path), lalu hapus file-nya — menukar putaran ke disk dengan puncak memori yang lebih rendah. (Untuk jalur in-memory yang digunakan di mana pun lain pada halaman ini, bentuk berbasis-byte adalah Laravel Attachment::fromData() / Symfony Email::attach($bytes, 'name.pdf', 'application/pdf').)
  • Jangan pernah menginterpolasi input pengguna yang tidak tervalidasi ke nama file lampiran. Berikan nilai yang Anda kendalikan (sebuah nomor faktur yang Anda hasilkan), agar client penerima tidak dapat dikemudikan oleh nama yang dibuat dengan sengaja.
  • Kirim setiap pelanggan hanya dokumennya sendiri. Bangun PDF dari record subjek yang terautentikasi di dalam job, bukan dari sebuah id yang diterima atas kepercayaan dari permintaan.
  • Pada jalur yang di-queue, catat kelas exception dan sebuah id korelasi saat gagal, jangan pernah pesan exception atau stack trace. Jangan pernah menulis blok catch kosong di sekitar build-and-send.