콘텐츠로 이동
getnextpdf.com

생성 PDF를 메일러 첨부로 이메일 보내기

청구서, 영수증, 또는 보고서를 이메일로 보내는 것은 생성 PDF로 하는 가장 흔한 일 중 하나입니다. 그것을 하는 깔끔한 방법은 문서를 빌드하고, 그 원시 바이트를 가져와서, 그 바이트를 곧장 메일러의 첨부 API에 건네는 것입니다. 그 어느 시점에도 디스크의 임시 파일이 필요하지 않습니다.

이것은 운영 방법 안내입니다. 프레임워크에서 메일을 보내는 방법은 이미 안다고 가정합니다. NextPDF 측은 한 번의 호출입니다: Document::getPdfData()는 원시 포터블 문서 형식(PDF) 바이트를 문자열로 반환합니다. 첨부 측은 전적으로 메일러의 것입니다 — 이 가이드는 Laravel의 Attachment::fromData()와 Symfony Mailer의 Email::attach()를 사용합니다.

NextPDF는 메일 헬퍼를 실어 보내지 않습니다. 문서에 “이 PDF를 이메일로 보내기” 메서드는 없으며, 그런 것을 보여 주는 예제는 의심해야 합니다. 첨부 API는 항상 프레임워크의 것입니다.

이 페이지는 PDF 안에 파일 임베드하기의 발신 측 대응물입니다. 그 가이드는 파일을 임베드된 스트림으로 PDF 안에 첨부하고, 이 가이드는 완성된 PDF를 이메일에 첨부합니다. 둘은 다른 작업입니다 — 혼동하지 마십시오.

그 밖에 무엇을 하든, 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: 메모리 내 바이트를 Mailable에 첨부하기

섹션 제목: “Laravel: 메모리 내 바이트를 Mailable에 첨부하기”

Laravel의 Attachment::fromData()는 원시 바이트를 반환하는 콜백과 파일 이름을 받습니다. 임시 파일은 없습니다. Mailable에 attachments()를 구현하고 하나의 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에 바이트 첨부하기

섹션 제목: “Symfony Mailer: Email에 바이트 첨부하기”

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)는 바이트를 직접 받습니다. 파트가 올바르게 타입 지정되도록 세 번째 인수로 '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 생성하기를 참조하십시오. 흔한 형태는 다음과 같습니다: 한 작업이 PDF를 생성하고 저장하고, 두 번째 작업(또는 성공 콜백)이 그것을 다시 읽어 메일로 보냅니다. 단일 작업 전반에 바이트를 메모리에 유지하면, 파일을 완전히 건너뜁니다.

  • 인라인이 아니라 첨부. 생성된 청구서나 보고서는 거의 항상 별개의 다운로드 가능한 파일이므로, 첨부하십시오. 인라인 콘텐츠(cid: 참조가 있는 Content-Disposition: inline)는 HTML 본문에 임베드하는 이미지를 위해 남겨 두십시오 — PDF는 본문 콘텐츠가 아닙니다.
  • 크기를 주의하십시오. 이메일 첨부는 전송 중 base64로 인코딩되며, 이는 페이로드를 대략 3분의 1만큼 부풀립니다. 많은 수신 서버는 인코딩 메시지를 약 10–25 MB로 제한합니다. 큰 보고서의 경우, 파일 자체 대신 서명된 다운로드 링크가 있는 짧은 알림 이메일을 첨부하고 PDF를 HTTP로 서빙하십시오 — 컨트롤러에서 생성 PDF 반환하기 참조.
  • 한 번 빌드하고, 한 번 첨부하십시오. 반환된 바이트를 한 번 변수에 캡처하고 그 문자열을 메시지에 재사용하십시오. 하나의 이메일을 위해 최종 출력 메서드를 반복해서 호출하지 마십시오.
  • 워커의 메모리. 전체 PDF를 메모리에 유지하는 것은 일반적인 청구서와 영수증에는 괜찮습니다. 제약된 워커의 매우 큰 문서의 경우, save()로 임시 경로에 저장하고, Laravel attach($path) / Symfony Email::attachFromPath($path)를 통해 첨부한 다음, 파일을 삭제하십시오 — 디스크 왕복을 더 낮은 메모리 최대치와 맞바꿉니다. (이 페이지의 다른 모든 곳에서 사용되는 메모리 내 경로의 경우, 바이트 기반 형식은 Laravel Attachment::fromData() / Symfony Email::attach($bytes, 'name.pdf', 'application/pdf')입니다.)
  • 검증되지 않은 사용자 입력을 첨부 파일 이름에 절대 보간하지 마십시오. 사용자가 제어하는 값(사용자가 생성한 청구서 번호)을 전달하여, 수신자의 클라이언트가 조작된 이름에 의해 조종될 수 없게 하십시오.
  • 각 고객에게 자신의 문서만 보내십시오. PDF를 요청에서 곧이곧대로 받은 id가 아니라, 작업 안에서 인증된 주체의 레코드로부터 빌드하십시오.
  • 큐 경로에서는, 실패 시 예외 메시지나 스택 트레이스가 아니라 예외 클래스와 상관 id를 로깅하십시오. 빌드 후 전송 주위에 빈 catch 블록을 절대 쓰지 마십시오.