Aller au contenu
getnextpdf.com

Envoyer un PDF généré par e-mail en pièce jointe du mailer

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.

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.

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

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.

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

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.

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

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: inline avec une référence cid:) 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 Laravel attach($path) / Symfony Email::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 Laravel Attachment::fromData() / Symfony Email::attach($bytes, 'name.pdf', 'application/pdf').)
  • 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 catch vide autour du build-et-envoi.