Skip to footer content
USING IRONPDF

How to Send a PDF File as an Email Attachment in C#

Automating document delivery is a requirement that surfaces in nearly every line-of-business .NET application. When an order is placed, an invoice must reach the customer within seconds. When a report runs overnight, stakeholders expect it in their inbox before they arrive at the office. The simplest, most universally supported delivery format is a PDF sent as an email attachment. This guide walks you through the complete end-to-end workflow in C# -- generating a PDF document in memory with IronPDF and then sending it as an email attachment using either MailKit or the built-in System.Net.Mail namespace, all without writing a single byte to disk.

How Do You Install the Required Packages?

Two packages power this workflow: a PDF generation library and an email sending library. Install both via the Package Manager Console in Visual Studio or through the .NET CLI.

Install-Package IronPdf
dotnet add package IronPdf
Install-Package MailKit
dotnet add package MailKit
Install-Package IronPdf
dotnet add package IronPdf
Install-Package MailKit
dotnet add package MailKit
SHELL

IronPDF brings a Chromium-based rendering engine that converts HTML, CSS, and JavaScript into pixel-perfect PDF documents. It runs on Windows, Linux, and macOS, which means the same code works in an ASP.NET Core web API, a background service, or an Azure Function. MailKit is the library Microsoft recommends for all new .NET email development -- it supports SMTP, IMAP, POP3, OAuth 2.0, and full MIME construction. The MailKit source and documentation are available on GitHub.

How Do You Generate a PDF Document in Memory?

The ChromePdfRenderer class is the entry point for HTML to PDF conversion. Pass an HTML string to RenderHtmlAsPdf and you get back a PdfDocument object that lives entirely in memory. Access the raw bytes through the BinaryData property -- this byte array is exactly what email attachment APIs expect.

using IronPdf;

var renderer = new ChromePdfRenderer();

string htmlContent = """
    <h1>Order Confirmation</h1>
    <p>Thank you for your purchase.</p>
    <table>
        <tr><th>Item</th><th>Qty</th><th>Price</th></tr>
        <tr><td>Widget A</td><td>2</td><td>$19.99</td></tr>
        <tr><td>Widget B</td><td>1</td><td>$59.99</td></tr>
    </table>
    <p><strong>Order Total: $99.97</strong></p>
    """;

PdfDocument pdf = renderer.RenderHtmlAsPdf(htmlContent);

// pdf.BinaryData holds the complete PDF as a byte array
byte[] pdfBytes = pdf.BinaryData;
Console.WriteLine($"PDF generated: {pdfBytes.Length} bytes");
using IronPdf;

var renderer = new ChromePdfRenderer();

string htmlContent = """
    <h1>Order Confirmation</h1>
    <p>Thank you for your purchase.</p>
    <table>
        <tr><th>Item</th><th>Qty</th><th>Price</th></tr>
        <tr><td>Widget A</td><td>2</td><td>$19.99</td></tr>
        <tr><td>Widget B</td><td>1</td><td>$59.99</td></tr>
    </table>
    <p><strong>Order Total: $99.97</strong></p>
    """;

PdfDocument pdf = renderer.RenderHtmlAsPdf(htmlContent);

// pdf.BinaryData holds the complete PDF as a byte array
byte[] pdfBytes = pdf.BinaryData;
Console.WriteLine($"PDF generated: {pdfBytes.Length} bytes");
$vbLabelText   $csharpLabel

The RenderHtmlAsPdf method parses the HTML using the same Chromium engine that powers Google Chrome, so tables, CSS Grid, Flexbox, and embedded fonts all render exactly as they do in a browser. The result is a PdfDocument whose BinaryData property returns the full PDF binary without any disk reads and writes. For documents that pull in external images or stylesheets, use the optional BasePath parameter to tell IronPDF where to resolve relative resource URLs -- this is covered in detail on the HTML file to PDF how-to page.

Setting Page Layout and Custom Headers

Before attaching the PDF, you may want to configure margins, headers, or footers. All layout options live on the RenderingOptions property:

using IronPdf;

var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.MarginTop    = 15;
renderer.RenderingOptions.MarginBottom = 15;
renderer.RenderingOptions.MarginLeft   = 12;
renderer.RenderingOptions.MarginRight  = 12;

renderer.RenderingOptions.HtmlHeader = new HtmlHeaderFooter
{
    HtmlFragment = "<div style='font-size:9pt;color:#666;text-align:right;'>Monthly Report</div>",
    DrawDividerLine = true
};

renderer.RenderingOptions.HtmlFooter = new HtmlHeaderFooter
{
    HtmlFragment = "<div style='font-size:8pt;text-align:center;'>{page} of {total-pages}</div>"
};

PdfDocument pdf = renderer.RenderHtmlAsPdf("<h1>Monthly Summary</h1><p>See attached data.</p>");
byte[] pdfBytes = pdf.BinaryData;
using IronPdf;

var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.MarginTop    = 15;
renderer.RenderingOptions.MarginBottom = 15;
renderer.RenderingOptions.MarginLeft   = 12;
renderer.RenderingOptions.MarginRight  = 12;

renderer.RenderingOptions.HtmlHeader = new HtmlHeaderFooter
{
    HtmlFragment = "<div style='font-size:9pt;color:#666;text-align:right;'>Monthly Report</div>",
    DrawDividerLine = true
};

renderer.RenderingOptions.HtmlFooter = new HtmlHeaderFooter
{
    HtmlFragment = "<div style='font-size:8pt;text-align:center;'>{page} of {total-pages}</div>"
};

PdfDocument pdf = renderer.RenderHtmlAsPdf("<h1>Monthly Summary</h1><p>See attached data.</p>");
byte[] pdfBytes = pdf.BinaryData;
$vbLabelText   $csharpLabel

Margins are in millimetres. The {page} and {total-pages} tokens are replaced at render time. The HTML string to PDF how-to page covers the full set of rendering options. You can also add watermarks or stamp text and images on the generated document before attaching it.

How Do You Attach a PDF to an Email with MailKit?

MailKit builds a MIME message tree directly, giving you full control over content type, encoding, and attachment metadata. The BodyBuilder helper class simplifies the common case of a text or HTML body with one or more file attachments.

using IronPdf;
using MailKit.Net.Smtp;
using MailKit.Security;
using MimeKit;

// Step 1 -- generate the PDF in memory
var renderer = new ChromePdfRenderer();
PdfDocument pdf = renderer.RenderHtmlAsPdf("<h1>Monthly Report</h1><p>Generated automatically.</p>");

// Step 2 -- build the email message
var message = new MimeMessage();
message.From.Add(new MailboxAddress("Reports Service", "reports@example.com"));
message.To.Add(new MailboxAddress("Alice Smith", "alice@example.com"));
message.Subject = "Your Monthly Report is Ready";

var builder = new BodyBuilder();
builder.TextBody = "Hello Alice,\n\nPlease find your monthly report attached.\n\nRegards,\nReports Service";
builder.HtmlBody = "<p>Hello Alice,</p><p>Please find your monthly report attached.</p>";

// Add the in-memory PDF as an attachment
builder.Attachments.Add("MonthlyReport.pdf", pdf.BinaryData, new ContentType("application", "pdf"));
message.Body = builder.ToMessageBody();

// Step 3 -- send via SMTP with TLS
using var client = new SmtpClient();
await client.ConnectAsync("smtp.example.com", 587, SecureSocketOptions.StartTls);
await client.AuthenticateAsync("username", "app-password");
await client.SendAsync(message);
await client.DisconnectAsync(true);
using IronPdf;
using MailKit.Net.Smtp;
using MailKit.Security;
using MimeKit;

// Step 1 -- generate the PDF in memory
var renderer = new ChromePdfRenderer();
PdfDocument pdf = renderer.RenderHtmlAsPdf("<h1>Monthly Report</h1><p>Generated automatically.</p>");

// Step 2 -- build the email message
var message = new MimeMessage();
message.From.Add(new MailboxAddress("Reports Service", "reports@example.com"));
message.To.Add(new MailboxAddress("Alice Smith", "alice@example.com"));
message.Subject = "Your Monthly Report is Ready";

var builder = new BodyBuilder();
builder.TextBody = "Hello Alice,\n\nPlease find your monthly report attached.\n\nRegards,\nReports Service";
builder.HtmlBody = "<p>Hello Alice,</p><p>Please find your monthly report attached.</p>";

// Add the in-memory PDF as an attachment
builder.Attachments.Add("MonthlyReport.pdf", pdf.BinaryData, new ContentType("application", "pdf"));
message.Body = builder.ToMessageBody();

// Step 3 -- send via SMTP with TLS
using var client = new SmtpClient();
await client.ConnectAsync("smtp.example.com", 587, SecureSocketOptions.StartTls);
await client.AuthenticateAsync("username", "app-password");
await client.SendAsync(message);
await client.DisconnectAsync(true);
$vbLabelText   $csharpLabel

builder.Attachments.Add accepts three arguments: the filename the recipient sees in their email client, the raw byte array from pdf.BinaryData, and a ContentType instance specifying the MIME type as application/pdf. The async SMTP methods keep the calling thread free while the network operation completes -- critical in ASP.NET Core controllers that handle dozens of concurrent requests.

SecureSocketOptions.StartTls negotiates an encrypted channel with the SMTP server on port 587. For Gmail, use an App Password rather than your account password: generate one under Google Account Security > App Passwords, then pass it to AuthenticateAsync. For Microsoft 365, configure OAuth 2.0 authentication via MailKit's SaslMechanismOAuth2 class if your tenant has disabled basic auth.

Sending to Multiple Recipients

To copy multiple people on the same email, add addresses to the To, Cc, or Bcc collections before calling SendAsync:

message.To.Add(new MailboxAddress("Alice Smith",   "alice@example.com"));
message.To.Add(new MailboxAddress("Bob Jones",     "bob@example.com"));
message.Cc.Add(new MailboxAddress("Carol Manager", "carol@example.com"));
message.To.Add(new MailboxAddress("Alice Smith",   "alice@example.com"));
message.To.Add(new MailboxAddress("Bob Jones",     "bob@example.com"));
message.Cc.Add(new MailboxAddress("Carol Manager", "carol@example.com"));
$vbLabelText   $csharpLabel

A single SendAsync call delivers the message to all addresses. MailKit batches the RCPT TO commands in one SMTP session, so there is no performance penalty for multiple recipients.

How Do You Use System.Net.Mail as an Alternative?

For projects targeting older .NET versions or projects where adding a third-party NuGet package is not permitted, the built-in System.Net.Mail namespace handles basic SMTP delivery. Microsoft no longer recommends it for new development, but it covers the common use cases without extra dependencies.

using IronPdf;
using System.Net;
using System.Net.Mail;

// Generate the PDF
var renderer = new ChromePdfRenderer();
PdfDocument pdf = renderer.RenderHtmlAsPdf("<h1>Invoice #1001</h1><p>Amount due: $350.00</p>");

// Build the mail message
using var message = new MailMessage("invoices@example.com", "customer@example.com");
message.Subject = "Invoice #1001 Attached";
message.Body    = "Your invoice is attached to this email. Please remit payment within 30 days.";
message.IsBodyHtml = false;

// Wrap the byte array in a MemoryStream for the Attachment constructor
var stream = new MemoryStream(pdf.BinaryData);
message.Attachments.Add(new Attachment(stream, "Invoice-1001.pdf", "application/pdf"));

// Send via SMTP
using var client = new SmtpClient("smtp.example.com", 587)
{
    Credentials = new NetworkCredential("username", "password"),
    EnableSsl   = true
};
await client.SendMailAsync(message);
using IronPdf;
using System.Net;
using System.Net.Mail;

// Generate the PDF
var renderer = new ChromePdfRenderer();
PdfDocument pdf = renderer.RenderHtmlAsPdf("<h1>Invoice #1001</h1><p>Amount due: $350.00</p>");

// Build the mail message
using var message = new MailMessage("invoices@example.com", "customer@example.com");
message.Subject = "Invoice #1001 Attached";
message.Body    = "Your invoice is attached to this email. Please remit payment within 30 days.";
message.IsBodyHtml = false;

// Wrap the byte array in a MemoryStream for the Attachment constructor
var stream = new MemoryStream(pdf.BinaryData);
message.Attachments.Add(new Attachment(stream, "Invoice-1001.pdf", "application/pdf"));

// Send via SMTP
using var client = new SmtpClient("smtp.example.com", 587)
{
    Credentials = new NetworkCredential("username", "password"),
    EnableSsl   = true
};
await client.SendMailAsync(message);
$vbLabelText   $csharpLabel

The key difference compared to MailKit is that System.Net.Mail.Attachment does not accept a byte array directly -- you must wrap pdf.BinaryData in a MemoryStream first. Both the MailMessage and SmtpClient are wrapped in using statements, which dispose the SMTP connection and flush the underlying stream after sending. If you omit the using on MailMessage, the attachment stream may be disposed before the send completes on some runtimes.

Choosing Between MailKit and System.Net.Mail

MailKit vs System.Net.Mail feature comparison
Feature MailKit System.Net.Mail
OAuth 2.0 authentication Yes No
IMAP / POP3 support Yes No
Async-first API Yes Partial
Microsoft recommendation Recommended Legacy
Additional NuGet package Required Not required
Complex MIME construction Full support Basic

Choose MailKit for any new project, especially if the SMTP server requires OAuth or if you need IMAP to read replies. Use System.Net.Mail when the codebase already depends on it and the migration cost is not justified.

How Do You Apply This Pattern to Real Business Workflows?

The in-memory PDF-to-email pattern applies directly to the document automation scenarios that drive most business applications.

Invoice Automation

An e-commerce order handler generates an invoice PDF immediately after payment is captured. The OrderConfirmed event triggers a method that calls RenderHtmlAsPdf with a Razor-templated HTML string populated from order data, then sends the result to the customer's email address. Because the PDF never touches the file system, there are no leftover files to clean up, no race conditions on a shared temp directory, and no permission issues in containerised deployments. For more on rendering HTML from Razor views, see the ASP.NET Core PDF generation guide.

Scheduled Report Distribution

A background service scheduled with IHostedService generates a weekly analytics summary at 06:00 every Monday. It queries the database, builds an HTML report string, renders it with IronPDF, and uses MailKit to send it to a distribution list. The entire pipeline runs as an async workflow, so it does not hold a thread pool thread during the SMTP handshake. For Azure-hosted workloads, the Azure PDF generator guide explains how to deploy IronPDF inside Azure App Service and Azure Functions.

Receipt Generation in ASP.NET Core

In an ASP.NET Core minimal API or controller action, a POST endpoint receives a checkout payload, generates a receipt PDF, and returns an HTTP 200 while simultaneously firing off the email. Keep the email-send logic in a background Task so the HTTP response returns to the client immediately:

app.MapPost("/checkout", async (CheckoutRequest req, IEmailService emailService) =>
{
    var renderer = new ChromePdfRenderer();
    PdfDocument receipt = renderer.RenderHtmlAsPdf(BuildReceiptHtml(req));

    // Fire and forget -- do not await so the HTTP response is immediate
    _ = emailService.SendReceiptAsync(req.CustomerEmail, receipt.BinaryData);

    return Results.Ok(new { message = "Order confirmed." });
});
app.MapPost("/checkout", async (CheckoutRequest req, IEmailService emailService) =>
{
    var renderer = new ChromePdfRenderer();
    PdfDocument receipt = renderer.RenderHtmlAsPdf(BuildReceiptHtml(req));

    // Fire and forget -- do not await so the HTTP response is immediate
    _ = emailService.SendReceiptAsync(req.CustomerEmail, receipt.BinaryData);

    return Results.Ok(new { message = "Order confirmed." });
});
$vbLabelText   $csharpLabel

This keeps the API response time below 100 ms even when the SMTP server is slow. The emailService is registered as a scoped or transient service that wraps the MailKit SmtpClient.

How Do You Handle Errors and Retries?

Network operations fail. SMTP servers are temporarily unavailable, authentication tokens expire, and attachment size limits vary by provider. Build resilience into the email-send path from the start.

Wrap the MailKit send logic in a try/catch and log failures to a persistent queue so they can be retried:

using IronPdf;
using MailKit.Net.Smtp;
using MailKit.Security;
using MimeKit;
using Microsoft.Extensions.Logging;

async Task SendPdfEmailWithRetryAsync(
    byte[] pdfBytes,
    string recipientEmail,
    string subject,
    ILogger logger,
    int maxAttempts = 3)
{
    for (int attempt = 1; attempt <= maxAttempts; attempt++)
    {
        try
        {
            var message = new MimeMessage();
            message.From.Add(new MailboxAddress("Mailer", "mailer@example.com"));
            message.To.Add(MailboxAddress.Parse(recipientEmail));
            message.Subject = subject;

            var builder = new BodyBuilder { TextBody = "Your document is attached." };
            builder.Attachments.Add("document.pdf", pdfBytes, new ContentType("application", "pdf"));
            message.Body = builder.ToMessageBody();

            using var smtpClient = new SmtpClient();
            await smtpClient.ConnectAsync("smtp.example.com", 587, SecureSocketOptions.StartTls);
            await smtpClient.AuthenticateAsync("user", "pass");
            await smtpClient.SendAsync(message);
            await smtpClient.DisconnectAsync(true);

            logger.LogInformation("Email sent to {Email} on attempt {Attempt}", recipientEmail, attempt);
            return;
        }
        catch (Exception ex) when (attempt < maxAttempts)
        {
            logger.LogWarning(ex, "Send attempt {Attempt} failed. Retrying...", attempt);
            await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
        }
    }
}
using IronPdf;
using MailKit.Net.Smtp;
using MailKit.Security;
using MimeKit;
using Microsoft.Extensions.Logging;

async Task SendPdfEmailWithRetryAsync(
    byte[] pdfBytes,
    string recipientEmail,
    string subject,
    ILogger logger,
    int maxAttempts = 3)
{
    for (int attempt = 1; attempt <= maxAttempts; attempt++)
    {
        try
        {
            var message = new MimeMessage();
            message.From.Add(new MailboxAddress("Mailer", "mailer@example.com"));
            message.To.Add(MailboxAddress.Parse(recipientEmail));
            message.Subject = subject;

            var builder = new BodyBuilder { TextBody = "Your document is attached." };
            builder.Attachments.Add("document.pdf", pdfBytes, new ContentType("application", "pdf"));
            message.Body = builder.ToMessageBody();

            using var smtpClient = new SmtpClient();
            await smtpClient.ConnectAsync("smtp.example.com", 587, SecureSocketOptions.StartTls);
            await smtpClient.AuthenticateAsync("user", "pass");
            await smtpClient.SendAsync(message);
            await smtpClient.DisconnectAsync(true);

            logger.LogInformation("Email sent to {Email} on attempt {Attempt}", recipientEmail, attempt);
            return;
        }
        catch (Exception ex) when (attempt < maxAttempts)
        {
            logger.LogWarning(ex, "Send attempt {Attempt} failed. Retrying...", attempt);
            await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
        }
    }
}
$vbLabelText   $csharpLabel

Exponential backoff -- 2 seconds after the first failure, 4 after the second -- prevents hammering an overloaded SMTP server. In production applications, replace the retry loop with a message queue (Azure Service Bus, RabbitMQ, or AWS SQS) so failures survive application restarts.

IronPDF also throws a PdfException if the HTML content cannot be rendered. Catch it separately from the SMTP exceptions so the error message is specific:

PdfDocument pdf;
try
{
    pdf = renderer.RenderHtmlAsPdf(htmlContent);
}
catch (IronPdf.Exceptions.PdfException ex)
{
    logger.LogError(ex, "PDF rendering failed");
    throw;
}
PdfDocument pdf;
try
{
    pdf = renderer.RenderHtmlAsPdf(htmlContent);
}
catch (IronPdf.Exceptions.PdfException ex)
{
    logger.LogError(ex, "PDF rendering failed");
    throw;
}
$vbLabelText   $csharpLabel

Separating rendering errors from delivery errors makes debugging faster. For a broader look at error handling in automated document pipelines, the 5-step PDF generation guide covers validation patterns in detail.

How Do You Keep Attachment Size Under Provider Limits?

Most commercial email providers enforce a maximum attachment size. Gmail limits individual attachments to 25 MB; Microsoft 365 defaults to 20 MB for standard mailboxes. A heavily styled HTML report with embedded images can exceed these limits unexpectedly.

Three techniques help stay within limits:

Compress images before rendering. Inline images should use compressed JPEG or WebP rather than uncompressed PNG. A 600 dpi PNG logo can add several megabytes to the PDF; a JPEG at 85% quality is typically under 200 KB for the same visual result.

Use IronPDF's compression settings. The PdfDocument.CompressImages method reduces the resolution of embedded bitmaps after rendering. Call it before reading BinaryData:

pdf.CompressImages(60); // quality 0-100
byte[] compressedPdfBytes = pdf.BinaryData;
pdf.CompressImages(60); // quality 0-100
byte[] compressedPdfBytes = pdf.BinaryData;
$vbLabelText   $csharpLabel

Split large reports into multiple emails. If a report exceeds the provider limit even after compression, generate one PDF per section and send each in a separate email. The PDF splitting and merging how-to page shows how to divide a PdfDocument by page range using CopyPages.

External references for SMTP size limits: Gmail attachment limits, Microsoft 365 message size limits.

What Are Your Next Steps?

You now have a working template for generating a PDF in memory with IronPDF and sending it as an email attachment using either MailKit or System.Net.Mail. The in-memory approach eliminates disk reads and writes, simplifies containerised deployments, and scales to high-throughput scenarios without requiring temporary file cleanup.

To deepen the integration:

Frequently Asked Questions

How can I send a generated PDF as an email attachment in C#?

Using IronPDF, you can send generated PDF files as email attachments by integrating its PDF creation capabilities with .NET's email-sending features.

What are the benefits of sending PDF files via email in .NET applications?

Sending PDF files via email in .NET applications helps automate document delivery, streamlining business workflows and enhancing customer communication.

Can IronPDF handle dynamic content in PDFs for email attachments?

Yes, IronPDF dynamically generates PDF content, making it suitable for event-driven applications that require sending customized PDFs as email attachments.

What parameters are commonly used in email-sending methods with IronPDF?

Common parameters include the email subject, the sender's information, and EventArgs, which ensure efficient processing in event-driven applications.

Why is IronPDF suitable for automating document delivery?

IronPDF is suitable for automating document delivery because it provides reliable PDF creation and integrates with C# email-sending capabilities.

Is it possible to schedule PDF email sending with IronPDF?

Yes, IronPDF can be integrated into scheduled tasks to automate sending PDF emails at specified times, improving workflow efficiency.

Does IronPDF support creating PDFs from various data sources for email attachments?

IronPDF supports creating PDFs from multiple data sources, allowing developers to generate detailed documents for email attachments.

How does IronPDF enhance email communication with customers?

By allowing the generation and sending of detailed PDF documents as attachments, IronPDF enhances the professionalism and clarity of email communication with customers.

Can IronPDF be used to send invoices and reports as PDF attachments?

Yes, IronPDF is well-suited for generating and sending invoices, reports, and other documents as PDF attachments, catering to various business needs.

What role does IronPDF play in improving business workflows?

IronPDF improves business workflows by enabling the creation and distribution of PDF documents, reducing manual intervention and errors.

Curtis Chau
Technical Writer

Curtis Chau holds a Bachelor’s degree in Computer Science (Carleton University) and specializes in front-end development with expertise in Node.js, TypeScript, JavaScript, and React. Passionate about crafting intuitive and aesthetically pleasing user interfaces, Curtis enjoys working with modern frameworks and creating well-structured, visually appealing manuals.

...

Read More

Iron Support Team

We're online 24 hours, 5 days a week.
Chat
Email
Call Me