Skip to main content
RapidDev - Software Development Agency
outsystems-tutorial

Sending Emails with Generated PDF Attachments in OutSystems

Generate the PDF as Binary Data using the Ultimate PDF Forge component's GeneratePDF action (see outsystems-pdf-generation), then build an EmailAttachment record with the Binary Data, FileName, and MimeType 'application/pdf', append it to an EmailAttachment List, and pass the list to Send_Email's Attachments parameter. Both PDF generation and Send_Email must be in the same Server Action.

What you'll learn

  • How to combine PDF generation and email sending in a single Server Action
  • Building the EmailAttachment structure with Binary Data, FileName, and MimeType
  • Passing multiple attachments to a single email using a List
  • Implementing a Timer-based batch pattern for sending invoices to multiple recipients
  • Handling errors so a failed PDF generation does not prevent other emails from sending
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Advanced10 min read50-70 minOutSystems 11 and ODCMarch 2026RapidDev Engineering Team
TL;DR

Generate the PDF as Binary Data using the Ultimate PDF Forge component's GeneratePDF action (see outsystems-pdf-generation), then build an EmailAttachment record with the Binary Data, FileName, and MimeType 'application/pdf', append it to an EmailAttachment List, and pass the list to Send_Email's Attachments parameter. Both PDF generation and Send_Email must be in the same Server Action.

Emails with PDF Attachments in OutSystems

Two of the most-requested OutSystems features — PDF generation and email sending — combine naturally for invoice delivery, report distribution, and document workflows. This tutorial builds on both the pdf-generation and send-email tutorials, focusing on the integration pattern: generating PDFs on demand, attaching them to emails, and the batch variant using Timers for sending to many recipients.

Prerequisites

  • Ultimate PDF Forge component installed and referenced (see outsystems-pdf-generation tutorial)
  • SMTP configured in Service Center or an ODC email connector set up (see outsystems-send-email tutorial)
  • An Invoice entity with InvoiceNumber, CustomerId, Amount, and a corresponding InvoiceLine entity
  • An InvoicePDFScreen created and accessible (Anonymous role for the headless renderer)

Step-by-step guide

1

Verify dependencies before combining the patterns

Before writing the combined Server Action, confirm both subsystems work independently: 1. PDF generation: open GenerateInvoicePDF Server Action (from pdf-generation tutorial), run it in the Debugger for a known InvoiceId, verify PDFContent Binary Data is populated (not NullBinaryData()). 2. Email sending: open SendInvoiceEmail Server Action (from send-email tutorial), run it for a test customer, verify delivery in Service Center → Monitoring → Email. Only proceed to the combined action once both work independently. Debugging a combined action where both subsystems are new makes root cause analysis much harder. If GenerateInvoicePDF is not yet created, follow the pdf-generation tutorial first and return here.

Expected result: Both GenerateInvoicePDF and a basic email send work independently. You are ready to combine them.

2

Create the SendInvoiceWithPDF Server Action

Logic tab → Server Actions → right-click → Add Server Action. Name it 'SendInvoiceWithPDF'. Add input parameters: - InvoiceId: Invoice Identifier - CustomerEmail: Text - CustomerName: Text Action flow: Start → GenerateInvoicePDF: InvoiceId = InvoiceId (output: PDFContent Binary Data) → Build EmailAttachment structure: Assign: Attachment.FileContent = GenerateInvoicePDF.PDFContent Attachment.FileName = 'Invoice_' + IntegerToText(InvoiceId) + '.pdf' Attachment.MimeType = 'application/pdf' ListAppend(AttachmentList, Attachment) → Send_Email: From: Site.SenderEmail To: CustomerEmail Subject: 'Your Invoice #' + IntegerToText(InvoiceNumber) Email: InvoiceEmail (screen) CustomerName: CustomerName InvoiceId: InvoiceId Attachments: AttachmentList → End

typescript
1/* Local Variables needed */
2Attachment: EmailAttachment (Structure)
3AttachmentList: EmailAttachment List
4
5/* Build attachment */
6Assign:
7 Attachment.FileContent = GenerateInvoicePDF.PDFContent
8 Attachment.FileName = "Invoice_" + IntegerToText(InvoiceId) + ".pdf"
9 Attachment.MimeType = "application/pdf"
10
11ListAppend(AttachmentList, Attachment)
12
13/* Send_Email */
14Send_Email:
15 From: Site.SenderEmail
16 To: CustomerEmail
17 Subject: "Your Invoice #" + IntegerToText(InvoiceNumber)
18 Email: InvoiceEmail
19 CustomerName: CustomerName
20 InvoiceId: InvoiceId
21 Attachments: AttachmentList

Expected result: SendInvoiceWithPDF generates the PDF, builds the attachment, and sends the email with the PDF attached. Verify in Service Center → Monitoring → Email.

3

Add error handling that distinguishes PDF failure from email failure

PDF generation and email delivery are independent failure modes. Handle them separately so one failure does not mask the other. Wrap the GeneratePDF call in an exception handler that logs and re-raises a specific exception: Exception Handler (AllExceptions) around GenerateInvoicePDF node: → LogError('InvoiceModule', 'SendInvoiceWithPDF', 'PDF generation failed: ' + ExceptionMessage) → Raise 'PDFGenerationFailed' User Exception with InvoiceId in message Wrap Send_Email in a separate exception handler: Exception Handler (AllExceptions) around Send_Email node: → LogError('InvoiceModule', 'SendInvoiceWithPDF', 'Email send failed: ' + ExceptionMessage) → Raise 'EmailSendFailed' User Exception with CustomerEmail in message In the calling Client Action: - Handle PDFGenerationFailed: show 'Could not generate PDF. Please try again.' - Handle EmailSendFailed: show 'PDF created but email could not be sent. Download the PDF manually.' This distinction matters because PDF failure means you should offer the download button, while email failure means the user can retry sending separately.

typescript
1/* Exception handlers in SendInvoiceWithPDF Server Action */
2
3/* Handler 1: wraps GenerateInvoicePDF call */
4Exception Handler (AllExceptions on GeneratePDF node)
5 --> LogError: "InvoiceModule", "PDF_Generate",
6 "PDF failed for Invoice " + IntegerToText(InvoiceId) + ": " + ExceptionMessage
7 --> Raise: PDFGenerationFailed
8 Message = "Could not generate PDF for invoice #" + IntegerToText(InvoiceId)
9 --> End
10
11/* Handler 2: wraps Send_Email call */
12Exception Handler (AllExceptions on Send_Email node)
13 --> LogError: "InvoiceModule", "Email_Send",
14 "Email failed to " + CustomerEmail + ": " + ExceptionMessage
15 --> Raise: EmailSendFailed
16 Message = "PDF was generated but email to " + CustomerEmail + " could not be sent."
17 --> End

Expected result: PDF failures and email failures produce distinct error messages in the UI. Service Center logs identify exactly which step failed and for which invoice/customer.

4

Build a Timer-based batch sending pattern

For sending invoices to many customers (e.g., end-of-month billing run), use a Timer rather than looping in a Server Action triggered by a user click. Create a Timer: Processes tab → Timers → Add Timer → 'MonthlyInvoiceSend'. Schedule: first of each month at 2 AM: '00 00 2 1 * *'. Link to Server Action: 'SendMonthlyInvoices'. Server Action: SendMonthlyInvoices: Start → Aggregate: GetUnsentInvoices Filter: Invoice.SentAt = NullDateTime() and Invoice.InvoiceDate <= CurrDate() Max Records: Site.InvoiceBatchSize → If: GetUnsentInvoices.List.Empty → [True] End → For Each Invoice in GetUnsentInvoices.List → SendInvoiceWithPDF: InvoiceId: Invoice.Invoice.Id CustomerEmail: Invoice.Customer.Email CustomerName: Invoice.Customer.Name → UpdateInvoice: Invoice.SentAt = CurrDateTime() → End Per-invoice exception handler inside the For Each: Exception Handler (AllExceptions) → LogError: 'SendMonthlyInvoices', ExceptionMessage + ' InvoiceId: ' + InvoiceId → Continue (do not let one failed invoice stop all others)

typescript
1/* Timer schedule: first of month at 2 AM */
2"00 00 2 1 * *"
3
4/* Server Action: SendMonthlyInvoices */
5Start
6 --> GetUnsentInvoices:
7 Filter: Invoice.SentAt = NullDateTime()
8 and Invoice.InvoiceDate <= CurrDate()
9 Max Records: Site.InvoiceBatchSize
10 --> If: GetUnsentInvoices.List.Empty --> End
11 --> For Each: Inv in GetUnsentInvoices.List
12 --> SendInvoiceWithPDF:
13 InvoiceId = Inv.Invoice.Id
14 CustomerEmail = Inv.Customer.Email
15 CustomerName = Inv.Customer.Name
16 --> UpdateInvoice:
17 Invoice.Id = Inv.Invoice.Id
18 Invoice.SentAt = CurrDateTime()
19 --> End
20
21 Exception Handler (AllExceptions) inside For Each
22 --> LogError: "InvoiceModule", "BatchInvoice",
23 "Failed: InvoiceId=" + IntegerToText(Inv.Invoice.Id)
24 + " Error: " + ExceptionMessage
25 --> Continue /* Process next invoice */

Expected result: The monthly timer runs at 2 AM on the first of each month, generates PDFs and sends emails for all unsent invoices, and marks each one as sent. Failed invoices are logged and processed in the next run.

5

Handle the ODC 5.5 MB PDF limit in batch sending

In ODC, each call to GeneratePDF is subject to a 5.5 MB file size limit. In a batch of 100 invoices, any invoice whose PDF exceeds this limit throws an exception. The per-invoice AllExceptions handler catches it and continues — but the customer does not receive their invoice. Solutions: 1. Monitor: check GeneratePDF.PDF binary size before attaching: If Length(GenerateInvoicePDF.PDFContent) > 5000000 then log a warning and send without attachment, notifying the customer to download the invoice from the app. 2. Optimize: reduce image sizes in the PDF template screen, remove large logos. 3. Split: for invoices with many line items, split into multiple smaller PDFs each under 5 MB. Implementation: After the GeneratePDF call, add: If: Length(GenerateInvoicePDF.PDFContent) > 5242880 (5 MB in bytes) [True] → Send_Email without attachments, body says 'Your invoice is available at [link]' [False] → proceed with attachment

typescript
1/* ODC size check after GeneratePDF */
2If: Length(GenerateInvoicePDF.PDFContent) > 5242880
3 [True] --> Send_Email:
4 Subject: "Your Invoice #" + ... + " (See link below)"
5 Email: InvoiceEmailNoAttachment
6 DownloadLink: InvoiceDownloadURL
7 /* No Attachments parameter */
8 [False] --> (proceed with full attachment as normal)

Expected result: In ODC, oversized PDFs trigger an alternative email with a download link instead of failing silently. The customer still receives their invoice.

Complete working example

EmailWithPDFAttachment_complete.txt
1/* ============================================================
2 DEPENDENCIES:
3 - Ultimate PDF Forge component (GeneratePDF action)
4 - InvoicePDFScreen (Template screen, Anonymous role)
5 - InvoiceEmail (Email screen with CustomerName, InvoiceId inputs)
6 - SMTP configured in Service Center
7 ============================================================ */
8
9/* ============================================================
10 SERVER ACTION: SendInvoiceWithPDF
11 Input: InvoiceId (Invoice Identifier),
12 CustomerEmail (Text), CustomerName (Text)
13 ============================================================ */
14Start
15 --> Assign:
16 ScreenURL = GetOwnerURLPath() + "InvoicePDFScreen?InvoiceId="
17 + IntegerToText(InvoiceId)
18 --> GeneratePDF (UltimatePDF):
19 URL: ScreenURL
20 WaitForSelector: ".invoice-ready"
21 Format: A4
22 Orientation: Portrait
23
24 Exception Handler (AllExceptions on GeneratePDF)
25 --> LogError: "InvoiceModule", "PDF", ExceptionMessage
26 --> Raise: PDFGenerationFailed
27 --> End
28
29 --> If: Length(GeneratePDF.PDF) > 5242880 /* ODC 5MB check */
30 [True] --> Send_Email (no attachment, with download link)
31 [False] --> Assign: Attachment.FileContent = GeneratePDF.PDF
32 Attachment.FileName = "Invoice_" + IntegerToText(InvoiceId) + ".pdf"
33 Attachment.MimeType = "application/pdf"
34 --> ListAppend(AttachmentList, Attachment)
35 --> Send_Email:
36 From: Site.SenderEmail
37 To: CustomerEmail
38 Subject: "Your Invoice #" + IntegerToText(InvoiceId)
39 Email: InvoiceEmail
40 CustomerName: CustomerName
41 InvoiceId: InvoiceId
42 Attachments: AttachmentList
43
44 Exception Handler (AllExceptions on Send_Email)
45 --> LogError: "InvoiceModule", "Email", ExceptionMessage
46 --> Raise: EmailSendFailed
47 --> End
48
49 --> End
50
51/* ============================================================
52 TIMER: MonthlyInvoiceSend
53 Schedule: '00 00 2 1 * *'
54 Action: SendMonthlyInvoices
55 ============================================================ */
56
57/* SERVER ACTION: SendMonthlyInvoices */
58Start
59 --> GetUnsentInvoices: (filter SentAt = NullDateTime())
60 --> If: empty --> End
61 --> For Each: Inv in list
62 --> SendInvoiceWithPDF: InvoiceId, Email, Name
63 --> UpdateInvoice: SentAt = CurrDateTime()
64
65 Exception Handler (AllExceptions inside For Each)
66 --> LogError with InvoiceId
67 --> Continue
68 --> End

Common mistakes

Why it's a problem: Generating the PDF and sending the email in separate Server Actions called sequentially from a Client Action

How to avoid: The Binary Data from GeneratePDF must be passed to Send_Email in the same Server Action call. You cannot return Binary Data from one Server Action to a Client Action and then pass it to another Server Action — the data transfer size through the client is limited. Keep both calls in one Server Action.

Why it's a problem: Not handling the case where GeneratePDF.PDF is NullBinaryData() before passing to Send_Email

How to avoid: If GeneratePDF fails silently (returns empty rather than throwing), Send_Email will attach a zero-byte file. Add an If node: If GeneratePDF.PDF = NullBinaryData() → raise a PDFGenerationFailed exception rather than sending a broken attachment.

Why it's a problem: Updating Invoice.SentAt before calling SendInvoiceWithPDF — marking it sent even if the send fails

How to avoid: Update SentAt only after a successful SendInvoiceWithPDF call. If the send fails, the exception handler fires and the update never executes, leaving the invoice eligible for the next batch run.

Why it's a problem: Not resetting AttachmentList between batch iterations in the timer loop

How to avoid: If AttachmentList is a screen-level or action-level variable initialized outside the For Each loop, it accumulates attachments across iterations. Initialize (or clear) the AttachmentList inside the For Each loop body so each email gets only its own invoice attached.

Best practices

  • Test the complete end-to-end flow (generate PDF, attach, send) in Development with a real email address before deploying to QA or Production.
  • Track email send status on the Invoice entity (SentAt, SentTo, AttachmentSizeBytes) for audit trails and resend capability.
  • Use a per-invoice exception handler inside the timer batch loop so one failing invoice does not prevent all subsequent invoices from sending.
  • Check PDF file size before attaching in ODC apps — implement the >5 MB fallback to a download link to prevent silent batch failures.
  • For high-volume sending, consider using a transactional email API (SendGrid, Postmark) via REST instead of SMTP — they provide better deliverability, open tracking, and bounce handling.
  • Store the generated PDF in the database (Invoice.PDFContent Binary Data) so users can download it again without re-generating, and for audit purposes.

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I'm combining PDF generation and email sending in OutSystems 11 Reactive Web. I have GenerateInvoicePDF Server Action (returns PDFContent Binary Data) and an InvoiceEmail Email screen. Show me: (1) the SendInvoiceWithPDF Server Action that generates the PDF, builds an EmailAttachment structure with MimeType 'application/pdf', calls ListAppend to add it to the Attachments list, and calls Send_Email with the attachment. (2) Separate exception handlers for PDF failure and email failure. (3) A timer-based batch sending pattern with per-invoice error handling using Continue.

OutSystems Prompt

Create the complete email-with-PDF-attachment pattern in OutSystems. Build: (1) SendInvoiceWithPDF Server Action with InvoiceId, CustomerEmail, CustomerName inputs that calls GenerateInvoicePDF, builds EmailAttachment (FileContent=PDFContent, FileName='Invoice_'+InvoiceId+'.pdf', MimeType='application/pdf'), calls ListAppend, then Send_Email with the attachment list. (2) Separate AllExceptions handlers for PDF and email steps that log and raise PDFGenerationFailed or EmailSendFailed. (3) SendMonthlyInvoices Server Action with For Each over unsent invoices, calling SendInvoiceWithPDF per invoice with AllExceptions+Continue inside the loop.

Frequently asked questions

Can I attach an Excel export and a PDF in the same email?

Yes. Create two Local Variables of type EmailAttachment, populate each with the respective Binary Data (one from RecordListToExcel, one from GeneratePDF), call ListAppend for each, and pass the list to Send_Email's Attachments parameter. There is no limit on the number of attachments, only the total message size enforced by the SMTP server (typically 10-25 MB).

How do I resend a failed invoice email without regenerating the PDF?

If you stored the generated PDF Binary Data in the Invoice entity (Invoice.PDFContent), you can retrieve it from the database and pass it directly to Send_Email without calling GeneratePDF again. This is faster for resends, provides a consistent attachment (same PDF as originally generated), and works even if the invoice data changes after the original send.

Can the PDF attachment be password-protected?

The Ultimate PDF Forge component does not natively support password-protected PDFs. For password protection, use a Forge component that wraps iTextSharp or a similar PDF library. Alternatively, for ODC use an External Library that calls a PDF security API. Password-protect before attaching — the Binary Data you pass to EmailAttachment can come from any source.

What happens if Send_Email succeeds but the email never reaches the recipient?

OutSystems' Send_Email queues the email for delivery via SMTP and reports success once the SMTP server accepts it. If the SMTP server later bounces or the recipient's server rejects it, OutSystems does not receive that feedback natively. For bounce handling, use a transactional email service (SendGrid, Postmark) via their API, which provides webhooks for bounces, opens, and clicks.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.