コンテンツにスキップ
getnextpdf.com

生成済み PDF をメーラーの添付ファイルとして送る

請求書、領収書、レポートをメールで送ることは、生成済み PDF でやることの中でも最も一般的なものの 1 つです。それを行うクリーンな方法は、ドキュメントを構築し、その生のバイトを取り、それらのバイトをメーラーの添付 API へ直接渡すことです。途中のどの時点でも、ディスク上の一時ファイルは不要です。

これはハウツーです。すでにフレームワークでメールを送る方法を知っていることを前提とします。NextPDF 側は単一の呼び出しです。Document::getPdfData() が生の Portable Document Format(PDF)バイトを文字列として返します。添付側は完全にあなたのメーラーのものです——このガイドは Laravel の Attachment::fromData() と Symfony Mailer の Email::attach() を使います。

NextPDF はメールヘルパーを 出荷しません。ドキュメント上に「この PDF をメールする」メソッドはなく、それを示すいかなる例も疑ってかかるべきです。添付 API は常にあなたのフレームワークのものです。

このページは、PDF 内へのファイル埋め込み の送信側の対応物です。そのガイドはファイルを埋め込みストリームとして PDF の中へ 添付します。このガイドは完成した PDF を メールに 添付します。これらは異なる操作です——2 つを混同しないでください。

他に何をするにせよ、NextPDF のステップは同じです。バイトを生成します。

<?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() はドキュメントを構築し、そのバイトを文字列として返します。ディスクには何も書かず、Hypertext Transfer Protocol(HTTP)ヘッダーも送りません——これはまさに添付に望むことです。

ドキュメントを NextPDF\Contracts\PdfDocumentInterface 型を通じてのみ保持している場合(たとえばフレームワーク統合が渡した値)は、代わりに契約レベルの相当物を使ってください。

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

output(dest: OutputDestination::String)PdfDocumentInterface で宣言され、ヘッダーを一切出さずに同じ生のバイトを返します。具体的な NextPDF\Core\Document を保持しているときは getPdfData() を、インターフェースだけを持つときは output(...) の形を使ってください。

Laravel または Symfony 統合を使う場合は、ドキュメントを直接構築するのではなく、コンテナから新しいドキュメントを解決してください——各フレームワークでの解決パスについては コントローラーから生成済み PDF を返す を参照してください。以下のすべては、ドキュメントをどう取得したかにかかわらず同じように機能します。

Laravel の Attachment::fromData() は、生のバイトを返すコールバックとファイル名を取ります。一時ファイルはありません。Mailable に attachments() を実装し、1 つの 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();
}
}

いつものように送信します。

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

fromData() のコールバックは、メッセージが構築されるときに遅延的に呼び出されるため、PDF は構築時ではなく送信時に生成されます。->withMime('application/pdf') を設定して、受信者のクライアントが拡張子から推測するのではなく、そのパートを PDF として扱うようにしてください。コントローラー内では、代わりに生のメッセージ上で $message->attachData($bytes, $name, ['mime' => 'application/pdf']) を呼べますが、Mailable 上の Attachment::fromData() が慣用的でモダンな形です。

Symfony Mailer の Email::attach() は、本体をメモリー内の文字列として、明示的なファイル名とコンテンツタイプとともに受け取ります。やはり、一時ファイルはありません。

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) はバイトを直接取ります。第 3 引数に 'application/pdf' を渡して、パートが正しく型付けされるようにしてください。ストリームから添付したい場合は attachFromPath() が存在しますが、生成されたコンテンツには、メモリー内の attach() の形がディスクへの不要な往復を避けます。

複数ページの PDF を構築することとメールを送ることは、どちらもリクエストスレッドで行うべきでないほど遅いものです。作業をキュー化してください。パターンは、ジョブをディスパッチする(または Laravel では Mailable 自体をキュー化する)こと、そしてワーカーで PDF を構築することです。

最もシンプルな Laravel の形は、Mailable を ShouldQueue にすることです。Attachment::fromData() のコールバックは、キュー化されたメッセージが構築されるときに走るため、PDF はディスパッチ時ではなくワーカーで生成されます。

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));

Symfony では、バイトではなく識別子を運ぶ Messenger メッセージをディスパッチし、ハンドラーにワーカーで PDF を構築してメールを送らせます。請求書 ID を渡し、ハンドラーでレコードを引き、上で示した InvoiceMailer を呼んでください。Symfony のメーラーは、SendEmailMessage 用に Messenger トランスポートが設定されていればすでに非同期なので、同期的に呼ばれた send() でもワーカーに転送され得ます。

専用の生成ジョブで PDF を生成してからメールする場合は、統合の GeneratePdfJob / GeneratePdfMessage のサーフェスとそのワーカー安全性のルールについて、キュー化されたジョブで PDF を生成する を参照してください。よくある形は、1 つのジョブが生成して PDF を保存し、2 つ目のジョブ(または成功コールバック)がそれを読み戻してメールするというものです。バイトを単一のジョブをまたいでメモリー内に保つときは、ファイルを完全に省けます。

  • インラインではなく添付。 生成された請求書やレポートは、ほとんど常に別個のダウンロード可能なファイルなので、添付してください。インラインコンテンツ(cid: 参照を伴う Content-Disposition: inline)は、HTML 本文に埋め込む画像のために取っておいてください——PDF は本文コンテンツではありません。
  • サイズに注意。 メールの添付ファイルは転送中に base64 エンコードされ、ペイロードを約 3 分の 1 増やします。多くの受信サーバーは、エンコード のメッセージをおよそ 10〜25 MB で上限とします。大きなレポートの場合は、ファイル自体の代わりに署名付きダウンロードリンクを含む短い通知メールを添付し、PDF を HTTP 越しに提供してください——コントローラーから生成済み PDF を返す を参照。
  • 1 回構築し、1 回添付する。 返されたバイトを変数に 1 回キャプチャし、その文字列をメッセージに再利用してください。1 通のメールのために最終出力メソッドを繰り返し呼ばないでください。
  • ワーカー上のメモリー。 完全な PDF をメモリー内に保つことは、典型的な請求書や領収書には問題ありません。制約のあるワーカー上の非常に大きなドキュメントには、save() で一時パスに保存し、Laravel の attach($path) / Symfony の Email::attachFromPath($path) で添付し、その後ファイルを削除してください——ディスクの往復を、より低いメモリーピークと引き換えにします。(このページの他のすべてで使われるメモリー内パスでは、バイトベースの形は Laravel の Attachment::fromData() / Symfony の Email::attach($bytes, 'name.pdf', 'application/pdf') です。)
  • 添付ファイル名に、検証していないユーザー入力を決して補間しないでください。あなたが制御する値(あなたが生成した請求書番号)を渡し、受信者のクライアントが細工された名前によって誘導されないようにしてください。
  • 各顧客には自分自身のドキュメントのみを送ってください。リクエストから鵜呑みにした ID からではなく、認証された主体のレコードからジョブ内で PDF を構築してください。
  • キュー化されたパスでは、失敗時に例外クラスと相関 ID をログに記録し、例外メッセージやスタックトレースは決して記録しないでください。構築と送信の周りに空の catch ブロックを決して書かないでください。