Envoyer un PDF généré par e-mail en pièce jointe du mailer
En un coup d’œil
Section intitulée « En un coup d’œil »Envoyer une facture, un reçu ou un rapport par e-mail est l’une des choses les plus courantes que tu fais avec un PDF généré. La façon propre de le faire est de construire le document, de prendre ses octets bruts, et de remettre ces octets droit à l’API de pièce jointe de ton mailer. Tu n’as besoin d’aucun fichier temporaire sur disque à aucun moment.
C’est un mode d’emploi. Il suppose que tu sais déjà envoyer du courrier dans ton
framework. Le côté NextPDF est un seul appel : Document::getPdfData() renvoie les
octets bruts au format Portable Document Format (PDF) sous forme de chaîne. Le côté pièce
jointe appartient entièrement à ton mailer — ce guide utilise Attachment::fromData() de
Laravel et Email::attach() de Symfony Mailer.
NextPDF ne livre pas de helper de courrier. Il n’y a pas de méthode « envoyer ce PDF par e-mail » sur un document, et tu devrais te méfier de tout exemple qui en montre une. L’API de pièce jointe est toujours celle de ton framework.
Cette page est le pendant sortant de l’embarquement de fichiers à l’intérieur d’un PDF. Ce guide-là attache des fichiers dans le PDF sous forme de flux embarqués ; ce guide-ci attache le PDF fini à un e-mail. Ce sont des opérations différentes — ne confonds pas les deux.
Obtenir les octets PDF bruts
Section intitulée « Obtenir les octets PDF bruts »Quoi que tu fasses d’autre, l’étape NextPDF est la même : produire les octets.
<?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() construit le document et renvoie ses octets sous forme de chaîne. Il
n’écrit rien sur disque et n’envoie aucun en-tête Hypertext Transfer Protocol (HTTP), ce
qui est exactement ce que tu veux pour une pièce jointe.
Si tu ne détiens le document que via le type
NextPDF\Contracts\PdfDocumentInterface (par exemple, une valeur que l’intégration de
framework t’a remise), utilise plutôt l’équivalent au niveau du contrat :
use NextPDF\Contracts\OutputDestination;
$bytes = $document->output(dest: OutputDestination::String);output(dest: OutputDestination::String) est déclaré sur PdfDocumentInterface et
renvoie les mêmes octets bruts sans émettre d’en-têtes. Utilise getPdfData() quand tu
détiens un NextPDF\Core\Document concret, et la forme output(...) quand tu n’as que
l’interface.
Si tu utilises l’intégration Laravel ou Symfony, résous un document neuf depuis le conteneur plutôt que d’en construire un directement — vois Renvoyer un PDF généré depuis un contrôleur pour le chemin de résolution dans chaque framework. Tout ce qui suit fonctionne de la même façon quelle que soit la manière dont tu as obtenu le document.
Laravel : attacher des octets en mémoire à un Mailable
Section intitulée « Laravel : attacher des octets en mémoire à un Mailable »Le Attachment::fromData() de Laravel prend un callback qui renvoie les octets bruts,
plus un nom de fichier. Il n’y a pas de fichier temporaire. Implémente attachments()
sur ton Mailable et renvoie un 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(); }}Envoie-le comme d’habitude :
use App\Mail\InvoiceMail;use Illuminate\Support\Facades\Mail;
Mail::to($invoice->customerEmail)->send(new InvoiceMail($invoice));Le callback de fromData() est invoqué paresseusement quand le message est construit,
donc le PDF est généré au moment de l’envoi, pas à la construction. Définis
->withMime('application/pdf') pour que le client du destinataire traite la partie comme
un PDF plutôt que de deviner depuis l’extension. À l’intérieur d’un contrôleur tu peux
plutôt appeler $message->attachData($bytes, $name, ['mime' => 'application/pdf']) sur
un message brut, mais Attachment::fromData() sur le Mailable est la forme moderne
idiomatique.
Symfony Mailer : attacher des octets à un Email
Section intitulée « Symfony Mailer : attacher des octets à un Email »Le Email::attach() de Symfony Mailer accepte le corps sous forme de chaîne en mémoire,
avec un nom de fichier et un type de contenu explicites. Là encore, pas de fichier
temporaire.
<?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) prend les octets
directement. Passe 'application/pdf' comme troisième argument pour que la partie soit
typée correctement. Si tu préfères attacher depuis un flux, attachFromPath() existe,
mais pour du contenu généré la forme attach() en mémoire évite un aller-retour inutile
au disque.
Le faire depuis un job mis en file
Section intitulée « Le faire depuis un job mis en file »Construire un PDF multi-pages et envoyer du courrier sont tous deux assez lents pour que tu ne doives pas les faire sur le thread de requête. Mets le travail en file. Le schéma est de dispatcher un job (ou, dans Laravel, de mettre le Mailable lui-même en file) et de construire le PDF sur le worker.
La forme Laravel la plus simple : rends le Mailable ShouldQueue. Parce que le callback
de Attachment::fromData() s’exécute quand le message mis en file est construit, le PDF
est généré sur le worker, pas au 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));Pour Symfony, dispatche un message Messenger portant les identifiants (pas les octets),
et laisse le handler construire le PDF et envoyer l’e-mail sur le worker. Passe un id de
facture, retrouve l’enregistrement dans le handler, et appelle l’InvoiceMailer montré
ci-dessus. Le mailer de Symfony est déjà asynchrone quand un transport Messenger est
configuré pour SendEmailMessage, donc même un send() appelé de façon synchrone peut
être transporté vers un worker.
Si tu génères le PDF dans un job de génération dédié puis l’envoies par e-mail, vois
Générer un PDF dans un job mis en file
pour la surface GeneratePdfJob / GeneratePdfMessage de l’intégration et ses règles de
sûreté du worker. Une forme courante est : un job génère et sauvegarde le PDF, un second
job (ou le callback de succès) le relit et l’envoie par e-mail. Quand tu gardes les
octets en mémoire au sein d’un seul job, tu évites le fichier entièrement.
Notes sur la taille, l’inline et les pièces jointes
Section intitulée « Notes sur la taille, l’inline et les pièces jointes »- Pièce jointe, pas inline. Une facture ou un rapport généré est presque toujours un
fichier téléchargeable séparé, donc attache-le. Réserve le contenu inline
(
Content-Disposition: inlineavec une référencecid:) aux images que tu embarques dans le corps HTML — un PDF n’est pas du contenu de corps. - Surveille la taille. Les pièces jointes d’e-mail sont encodées en base64 en transit, ce qui gonfle la charge utile d’environ un tiers. Beaucoup de serveurs destinataires plafonnent un message à environ 10–25 Mo après encodage. Pour un gros rapport, attache un court e-mail de notification avec un lien de téléchargement signé au lieu du fichier lui-même, et sers le PDF via HTTP — vois Renvoyer un PDF généré depuis un contrôleur.
- Construis une fois, attache une fois. Capture les octets renvoyés une fois dans une variable et réutilise cette chaîne pour le message. N’appelle pas la méthode de sortie finale à répétition pour un seul e-mail.
- Mémoire sur le worker. Garder le PDF complet en mémoire convient pour les factures
et reçus typiques. Pour de très gros documents sur un worker contraint, sauvegarde vers
un chemin temporaire avec
save(), attache via Laravelattach($path)/ SymfonyEmail::attachFromPath($path), puis supprime le fichier — en échangeant un aller-retour disque contre un pic de mémoire plus bas. (Pour le chemin en mémoire utilisé partout ailleurs sur cette page, la forme basée sur les octets est LaravelAttachment::fromData()/ SymfonyEmail::attach($bytes, 'name.pdf', 'application/pdf').)
Notes de sécurité
Section intitulée « Notes de sécurité »- N’interpole jamais d’entrée utilisateur non validée dans le nom de fichier de la pièce jointe. Passe une valeur que tu contrôles (un numéro de facture que tu as généré), pour que le client du destinataire ne puisse pas être orienté par un nom forgé.
- N’envoie à chaque client que son propre document. Construis le PDF à partir des enregistrements du sujet authentifié à l’intérieur du job, pas à partir d’un id pris sur parole depuis la requête.
- Dans un chemin mis en file, journalise la classe d’exception et un id de corrélation en
cas d’échec, jamais le message d’exception ni une trace de pile. N’écris jamais un bloc
catchvide autour du build-et-envoi.
Voir aussi
Section intitulée « Voir aussi »- Embarquer des fichiers et créer des portefeuilles PDF — l’inverse : attacher des fichiers dans le PDF.
- Générer un PDF dans un job mis en file — déplace la génération hors du thread de requête.
- Renvoyer un PDF généré depuis un contrôleur — sers le PDF via HTTP au lieu de l’attacher.
- Utilisation en production avec Laravel — résoudre un document depuis le conteneur et la sûreté du worker.
- Utilisation en production avec Symfony — les workers Messenger et la fabrique de documents.