Envie por e-mail um PDF gerado como anexo de mailer
Visão geral
Seção intitulada “Visão geral”Enviar por e-mail uma fatura, recibo ou relatório é uma das coisas mais comuns que você faz com um PDF gerado. A forma limpa de fazer isso é construir o documento, pegar seus bytes brutos e entregá-los diretamente à API de anexos do seu mailer. Você não precisa de um arquivo temporário no disco em momento algum.
Este é um how-to. Ele assume que você já sabe como enviar e-mail no seu framework.
O lado do NextPDF é uma única chamada: Document::getPdfData() retorna os bytes
brutos do Portable Document Format (PDF) como uma string. O lado do anexo pertence
inteiramente ao seu mailer — este guia usa Attachment::fromData() do Laravel e
Email::attach() do Symfony Mailer.
O NextPDF não traz um helper de e-mail. Não há método “envie este PDF por e-mail” em um documento, e você deve desconfiar de qualquer exemplo que mostre um. A API de anexo é sempre a do seu framework.
Esta página é a contraparte de saída de incorporar arquivos dentro de um PDF. Aquele guia anexa arquivos dentro do PDF como streams incorporados; este guia anexa o PDF finalizado a um e-mail. São operações diferentes — não confunda as duas.
Obtenha os bytes brutos do PDF
Seção intitulada “Obtenha os bytes brutos do PDF”Faça o que mais fizer, o passo do NextPDF é o mesmo: produzir os 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() constrói o documento e retorna seus bytes como uma string. Ele não
escreve nada no disco e não envia nenhum cabeçalho Hypertext Transfer Protocol
(HTTP), que é exatamente o que você quer para um anexo.
Se você tem o documento apenas por meio do tipo
NextPDF\Contracts\PdfDocumentInterface (por exemplo, um valor que a integração de
framework lhe entregou), use o equivalente no nível do contrato em vez disso:
use NextPDF\Contracts\OutputDestination;
$bytes = $document->output(dest: OutputDestination::String);output(dest: OutputDestination::String) é declarado em
PdfDocumentInterface e retorna os mesmos bytes brutos sem emitir nenhum cabeçalho.
Use getPdfData() quando você tem um NextPDF\Core\Document concreto, e a forma
output(...) quando você tem apenas a interface.
Se você usa a integração do Laravel ou do Symfony, resolva um documento novo a partir do contêiner em vez de construir um diretamente — consulte Retorne um PDF gerado a partir de um controller para o caminho de resolução em cada framework. Tudo abaixo funciona da mesma forma independentemente de como você obteve o documento.
Laravel: anexe bytes em memória a um Mailable
Seção intitulada “Laravel: anexe bytes em memória a um Mailable”O Attachment::fromData() do Laravel recebe um callback que retorna os bytes
brutos, mais um nome de arquivo. Não há arquivo temporário. Implemente
attachments() no seu Mailable e retorne um Attachment.
<?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(); }}Envie-o como de costume:
use App\Mail\InvoiceMail;use Illuminate\Support\Facades\Mail;
Mail::to($invoice->customerEmail)->send(new InvoiceMail($invoice));O callback de fromData() é invocado de forma preguiçosa quando a mensagem é
construída, então o PDF é gerado na hora do envio, não na construção. Defina
->withMime('application/pdf') para que o cliente do destinatário trate a parte
como um PDF em vez de adivinhar pela extensão. Dentro de um controller você pode, em
vez disso, chamar $message->attachData($bytes, $name, ['mime' => 'application/pdf'])
em uma mensagem bruta, mas Attachment::fromData() no Mailable é a forma moderna
idiomática.
Symfony Mailer: anexe bytes a um Email
Seção intitulada “Symfony Mailer: anexe bytes a um Email”O Email::attach() do Symfony Mailer aceita o corpo como uma string em memória,
com um nome de arquivo e content type explícitos. De novo, nenhum arquivo
temporário.
<?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) recebe os bytes
diretamente. Passe 'application/pdf' como terceiro argumento para que a parte seja
tipada corretamente. Se você prefere anexar a partir de um stream, attachFromPath()
existe, mas para conteúdo gerado a forma em memória attach() evita uma viagem
desnecessária ao disco.
Faça a partir de um job em fila
Seção intitulada “Faça a partir de um job em fila”Construir um PDF de várias páginas e enviar e-mail são ambos lentos o suficiente para que você não deva fazê-los na thread de requisição. Coloque o trabalho na fila. O padrão é despachar um job (ou, no Laravel, colocar o próprio Mailable na fila) e construir o PDF no worker.
A forma mais simples do Laravel: torne o Mailable ShouldQueue. Como o callback de
Attachment::fromData() roda quando a mensagem em fila é construída, o PDF é gerado
no worker, não no despacho.
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));Para o Symfony, despache uma mensagem do Messenger carregando os identificadores
(não os bytes), e deixe o handler construir o PDF e enviar o e-mail no worker.
Passe um id de fatura, busque o registro no handler e chame o InvoiceMailer
mostrado acima. O mailer do Symfony já é assíncrono quando um transporte do
Messenger está configurado para SendEmailMessage, então mesmo um send() chamado
de forma síncrona pode ser transportado para um worker.
Se você gera o PDF em um job de geração dedicado e depois o envia por e-mail,
consulte
Gere um PDF em um job em fila
para a superfície GeneratePdfJob / GeneratePdfMessage da integração e suas
regras de segurança no worker. Um formato comum é: um job gera e salva o PDF, um
segundo job (ou o callback de sucesso) o relê e o envia por e-mail. Quando você
mantém os bytes em memória ao longo de um único job, você pula o arquivo por
completo.
Notas sobre tamanho, inline e anexo
Seção intitulada “Notas sobre tamanho, inline e anexo”- Anexo, não inline. Uma fatura ou relatório gerado é quase sempre um arquivo
baixável separado, então anexe-o. Reserve o conteúdo inline
(
Content-Disposition: inlinecom uma referênciacid:) para imagens que você incorpora no corpo HTML — um PDF não é conteúdo de corpo. - Cuidado com o tamanho. Anexos de e-mail são codificados em base64 em trânsito, o que infla o payload em cerca de um terço. Muitos servidores receptores limitam uma mensagem em torno de 10–25 MB após a codificação. Para um relatório grande, anexe um e-mail curto de notificação com um link de download assinado em vez do arquivo em si, e sirva o PDF por HTTP — consulte Retorne um PDF gerado a partir de um controller.
- Construa uma vez, anexe uma vez. Capture os bytes retornados uma vez em uma variável e reutilize essa string para a mensagem. Não chame o método de saída final repetidamente para um e-mail.
- Memória no worker. Manter o PDF inteiro em memória está ok para faturas e
recibos típicos. Para documentos muito grandes em um worker restrito, salve em um
caminho temporário com
save(), anexe viaattach($path)do Laravel /Email::attachFromPath($path)do Symfony, e então apague o arquivo — trocando uma viagem ao disco por um pico de memória menor. (Para o caminho em memória usado em todo o resto desta página, a forma baseada em bytes éAttachment::fromData()do Laravel /Email::attach($bytes, 'name.pdf', 'application/pdf')do Symfony.)
Notas de segurança
Seção intitulada “Notas de segurança”- Nunca interpole entrada de usuário não validada no nome do arquivo de anexo. Passe um valor que você controla (um número de fatura que você gerou), para que o cliente do destinatário não possa ser direcionado por um nome forjado.
- Envie a cada cliente apenas o documento dele. Construa o PDF a partir dos registros do sujeito autenticado dentro do job, não a partir de um id tomado por confiança a partir da requisição.
- Em um caminho em fila, registre a classe da exceção e um id de correlação em caso
de falha, nunca a mensagem da exceção ou um stack trace. Nunca escreva um bloco
catchvazio em torno do build-and-send.
Veja também
Seção intitulada “Veja também”- Incorpore arquivos e crie portfólios de PDF — o inverso: anexar arquivos dentro do PDF.
- Gere um PDF em um job em fila — mova a geração para fora da thread de requisição.
- Retorne um PDF gerado a partir de um controller — sirva o PDF por HTTP em vez de anexá-lo.
- Uso em produção do Laravel — resolvendo um documento a partir do contêiner e segurança no worker.
- Uso em produção do Symfony — workers do Messenger e a factory de documentos.