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
Verify dependencies before combining the patterns
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.
Create the SendInvoiceWithPDF Server Action
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
1/* Local Variables needed */2Attachment: EmailAttachment (Structure)3AttachmentList: EmailAttachment List45/* Build attachment */6Assign:7 Attachment.FileContent = GenerateInvoicePDF.PDFContent8 Attachment.FileName = "Invoice_" + IntegerToText(InvoiceId) + ".pdf"9 Attachment.MimeType = "application/pdf"1011ListAppend(AttachmentList, Attachment)1213/* Send_Email */14Send_Email:15 From: Site.SenderEmail16 To: CustomerEmail17 Subject: "Your Invoice #" + IntegerToText(InvoiceNumber)18 Email: InvoiceEmail19 CustomerName: CustomerName20 InvoiceId: InvoiceId21 Attachments: AttachmentListExpected result: SendInvoiceWithPDF generates the PDF, builds the attachment, and sends the email with the PDF attached. Verify in Service Center → Monitoring → Email.
Add error handling that distinguishes PDF failure from email failure
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.
1/* Exception handlers in SendInvoiceWithPDF Server Action */23/* Handler 1: wraps GenerateInvoicePDF call */4Exception Handler (AllExceptions on GeneratePDF node)5 --> LogError: "InvoiceModule", "PDF_Generate",6 "PDF failed for Invoice " + IntegerToText(InvoiceId) + ": " + ExceptionMessage7 --> Raise: PDFGenerationFailed8 Message = "Could not generate PDF for invoice #" + IntegerToText(InvoiceId)9 --> End1011/* Handler 2: wraps Send_Email call */12Exception Handler (AllExceptions on Send_Email node)13 --> LogError: "InvoiceModule", "Email_Send",14 "Email failed to " + CustomerEmail + ": " + ExceptionMessage15 --> Raise: EmailSendFailed16 Message = "PDF was generated but email to " + CustomerEmail + " could not be sent."17 --> EndExpected 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.
Build a Timer-based batch sending pattern
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)
1/* Timer schedule: first of month at 2 AM */2"00 00 2 1 * *"34/* Server Action: SendMonthlyInvoices */5Start6 --> GetUnsentInvoices:7 Filter: Invoice.SentAt = NullDateTime()8 and Invoice.InvoiceDate <= CurrDate()9 Max Records: Site.InvoiceBatchSize10 --> If: GetUnsentInvoices.List.Empty --> End11 --> For Each: Inv in GetUnsentInvoices.List12 --> SendInvoiceWithPDF:13 InvoiceId = Inv.Invoice.Id14 CustomerEmail = Inv.Customer.Email15 CustomerName = Inv.Customer.Name16 --> UpdateInvoice:17 Invoice.Id = Inv.Invoice.Id18 Invoice.SentAt = CurrDateTime()19 --> End2021 Exception Handler (AllExceptions) inside For Each22 --> LogError: "InvoiceModule", "BatchInvoice",23 "Failed: InvoiceId=" + IntegerToText(Inv.Invoice.Id)24 + " Error: " + ExceptionMessage25 --> 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.
Handle the ODC 5.5 MB PDF limit in batch sending
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
1/* ODC size check after GeneratePDF */2If: Length(GenerateInvoicePDF.PDFContent) > 52428803 [True] --> Send_Email:4 Subject: "Your Invoice #" + ... + " (See link below)"5 Email: InvoiceEmailNoAttachment6 DownloadLink: InvoiceDownloadURL7 /* 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
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 Center7 ============================================================ */89/* ============================================================10 SERVER ACTION: SendInvoiceWithPDF11 Input: InvoiceId (Invoice Identifier),12 CustomerEmail (Text), CustomerName (Text)13 ============================================================ */14Start15 --> Assign:16 ScreenURL = GetOwnerURLPath() + "InvoicePDFScreen?InvoiceId="17 + IntegerToText(InvoiceId)18 --> GeneratePDF (UltimatePDF):19 URL: ScreenURL20 WaitForSelector: ".invoice-ready"21 Format: A422 Orientation: Portrait2324 Exception Handler (AllExceptions on GeneratePDF)25 --> LogError: "InvoiceModule", "PDF", ExceptionMessage26 --> Raise: PDFGenerationFailed27 --> End2829 --> If: Length(GeneratePDF.PDF) > 5242880 /* ODC 5MB check */30 [True] --> Send_Email (no attachment, with download link)31 [False] --> Assign: Attachment.FileContent = GeneratePDF.PDF32 Attachment.FileName = "Invoice_" + IntegerToText(InvoiceId) + ".pdf"33 Attachment.MimeType = "application/pdf"34 --> ListAppend(AttachmentList, Attachment)35 --> Send_Email:36 From: Site.SenderEmail37 To: CustomerEmail38 Subject: "Your Invoice #" + IntegerToText(InvoiceId)39 Email: InvoiceEmail40 CustomerName: CustomerName41 InvoiceId: InvoiceId42 Attachments: AttachmentList4344 Exception Handler (AllExceptions on Send_Email)45 --> LogError: "InvoiceModule", "Email", ExceptionMessage46 --> Raise: EmailSendFailed47 --> End4849 --> End5051/* ============================================================52 TIMER: MonthlyInvoiceSend53 Schedule: '00 00 2 1 * *'54 Action: SendMonthlyInvoices55 ============================================================ */5657/* SERVER ACTION: SendMonthlyInvoices */58Start59 --> GetUnsentInvoices: (filter SentAt = NullDateTime())60 --> If: empty --> End61 --> For Each: Inv in list62 --> SendInvoiceWithPDF: InvoiceId, Email, Name63 --> UpdateInvoice: SentAt = CurrDateTime()6465 Exception Handler (AllExceptions inside For Each)66 --> LogError with InvoiceId67 --> Continue68 --> EndCommon 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.
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.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation