Email Multiple Attachments Odoo 18
By Braincuber Team
Published on December 28, 2025
Sales teams sending customer documents create manual chaos: sales rep confirms order, generates quotation PDF, downloads to computer, discovers invoice also needed, generates invoice PDF, downloads second file, opens Gmail manually, attaches quotation file, attaches invoice file, types email body remembering customer name and order number, clicks send, repeats this 15-step process 50 times weekly wasting 3+ hours—creating human error opportunities (forgetting attachments, attaching wrong files, misspelling customer names) and productivity drain from repetitive manual document gathering without automated multi-attachment email workflow.
Odoo 18 Email Multiple Attachments enable automated document sending through Python method generating multiple PDFs (quotation/invoice/reports), ir.attachment creation storing PDFs temporarily, base64 encoding for email transmission, mail.compose.message integration opening pre-populated composer, email template placeholders for personalization, attachment_ids field accepting list of attachment IDs, and single-button workflow—reducing 15-step manual process to 1-click automation saving 90% time while eliminating attachment errors through systematic PDF generation and delivery.
Multi-Attachment Features: PDF generation, Multiple documents, Automatic attachment creation, Email composer integration, Template placeholders, One-click send, Error elimination, Audit trail
Use Case: Sale Order + Invoice Email
Send both quotation and invoice PDFs in single email:
- Scenario: Customer confirms order, needs quotation AND invoice together
- Manual Process: Generate 2 PDFs separately, download, attach to email manually
- Automated Solution: Single button generates both PDFs, opens email composer with attachments pre-loaded
Implementation Overview
Three components required:
- Python Method: Generate PDFs, create attachments, open composer
- XML View: Add button to Sale Order form
- Email Template: Define email content with placeholders
Python Code - Model
Extend Sale Order model with multi-attachment method:
from odoo import models, _
from odoo.exceptions import ValidationError
import base64
class SaleOrder(models.Model):
_inherit = 'sale.order'
def action_send_so_invoice_email(self):
"""Generate and send email with quotation PDF and invoice PDF attached.
Steps:
1. Fetch email template
2. Generate Sale Order PDF
3. Generate linked invoice PDF
4. Open email composer with both attachments
"""
for order in self:
# Fetch email template
template = self.env.ref(
'custom_module_name.email_template_so_invoice',
raise_if_not_found=False
)
if not template:
raise ValidationError(_("Email template not found."))
attachments = []
# Generate Sale Order PDF
so_pdf, _ = self.env['ir.actions.report']._render_qweb_pdf(
'sale.action_report_saleorder', order.id
)
so_attachment = self.env['ir.attachment'].create({
'name': f"Quotation_{order.name}.pdf",
'type': 'binary',
'datas': base64.b64encode(so_pdf),
'res_model': 'sale.order',
'res_id': order.id,
'mimetype': 'application/pdf',
})
attachments.append(so_attachment.id)
# Generate Invoice PDF (first linked invoice)
invoice = order.invoice_ids[:1]
if invoice:
inv_pdf, _ = self.env['ir.actions.report']._render_qweb_pdf(
'account.report_invoice_with_payments', invoice.id
)
inv_attachment = self.env['ir.attachment'].create({
'name': f"Invoice_{invoice.name}.pdf",
'type': 'binary',
'datas': base64.b64encode(inv_pdf),
'res_model': 'sale.order',
'res_id': order.id,
'mimetype': 'application/pdf',
})
attachments.append(inv_attachment.id)
# Open email composer with attachments
return {
'type': 'ir.actions.act_window',
'res_model': 'mail.compose.message',
'view_mode': 'form',
'target': 'new',
'context': {
'default_model': 'sale.order',
'default_res_ids': [order.id],
'default_use_template': bool(template),
'default_template_id': template.id if template else False,
'default_attachment_ids': [(6, 0, attachments)],
},
}Code Explanation
1. Template Reference:
template = self.env.ref('custom_module_name.email_template_so_invoice')Fetches email template defined in XML. Raises error if not found ensuring safe template usage.
2. PDF Generation:
so_pdf, _ = self.env['ir.actions.report']._render_qweb_pdf(
'sale.action_report_saleorder', order.id
)_render_qweb_pdf: Generates PDF from report template
Returns: PDF binary data (tuple: pdf_content, format)
3. Attachment Creation:
so_attachment = self.env['ir.attachment'].create({
'name': f"Quotation_{order.name}.pdf",
'type': 'binary',
'datas': base64.b64encode(so_pdf),
'res_model': 'sale.order',
'res_id': order.id,
'mimetype': 'application/pdf',
})base64.b64encode: Converts binary PDF to base64 for storage
res_model/res_id: Links attachment to source record
4. Composer Integration:
'context': {
'default_attachment_ids': [(6, 0, attachments)],
}(6, 0, list): Odoo ORM command replacing all records
attachments list: Contains both quotation and invoice attachment IDs
XML Code - View & Template
Button Addition:
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Button on Sale Order form -->
<record id="view_order_form" model="ir.ui.view">
<field name="name">sale.order.form.so.invoice.button</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<header position="inside">
<button name="action_send_so_invoice_email"
type="object"
string="Send SO + Invoice Email"
class="btn-primary"
invisible="state != 'sale'"/>
</header>
</field>
</record>
</odoo>Email Template:
<record id="email_template_so_invoice" model="mail.template">
<field name="name">Sale Order + Invoice Email</field>
<field name="model_id" ref="sale.model_sale_order"/>
<field name="subject">Documents for {{ object.name }}</field>
<field name="email_from">{{ object.user_id.email or '' }}</field>
<field name="email_to">{{ object.partner_id.email }}</field>
<field name="body_html">
<![CDATA[
<p>Dear <t t-out="object.partner_id.name"/>,</p>
<p>Please find attached your quotation and invoice.</p>
<p>Best regards,<br/><t t-out="object.company_id.name"/></p>
]]>
</field>
</record>Template Placeholders
| Placeholder | Value |
|---|---|
| {{ object.name }} | Sale Order reference (e.g., SO012) |
| {{ object.partner_id.name }} | Customer name |
| {{ object.partner_id.email }} | Customer email address |
| {{ object.user_id.email }} | Salesperson email |
| {{ object.company_id.name }} | Company name |
Complete Workflow
- Sales rep confirms Sale Order (state changes to 'sale')
- "Send SO + Invoice Email" button appears in header
- Click button
- Python method executes:
- Generates quotation PDF
- Creates ir.attachment for quotation
- Finds first linked invoice
- Generates invoice PDF
- Creates ir.attachment for invoice
- Collects both attachment IDs
- Email composer opens with:
- Template subject filled
- Template body filled
- Customer email in "To" field
- Both PDFs attached
- User reviews/edits email (optional)
- Click Send
- Email delivered to customer with both documents
- Email logged in Sale Order chatter
Module Installation
- Create custom module:
sale_multi_attachment_email - Add files:
models/sale_order.py(Python code)views/sale_order_views.xml(Button XML)data/mail_template.xml(Template XML)__manifest__.py
- Install module in Odoo
- "Send SO + Invoice Email" button appears on confirmed Sale Orders
Adapting for Other Models
Apply same pattern to other document combinations:
Purchase Order + Vendor Bill:
class PurchaseOrder(models.Model):
_inherit = 'purchase.order'
def action_send_po_bill_email(self):
# Generate PO PDF + Bill PDF
# Attach both, send to vendorProject + Timesheet Report:
class Project(models.Model):
_inherit = 'project.project'
def action_send_project_timesheet_email(self):
# Generate project summary PDF + timesheet PDF
# Attach both, send to clientBest Practices
Check Document Existence Before PDF Generation: Generating invoice PDF when no invoice exists = error crash. Always check: if invoice: before _render_qweb_pdf. Prevents 500 errors when button clicked on orders without invoices yet created.
Use Meaningful Attachment Names: Generic name "document.pdf" confusing in customer download folder. Specific names: f"Quotation_{order.name}.pdf" = "Quotation_SO012.pdf". Customer knows exactly what each file contains when reviewing multiple orders.
Link Attachments to Source Record: Setting res_model and res_id = attachments appear in record's attachment panel. Enables: viewing sent documents later, audit trail showing when PDF generated, cleanup when record deleted preventing orphaned files.
Conclusion
Odoo 18 Email Multiple Attachments automate document sending through Python PDF generation, ir.attachment creation, and mail composer integration. Reduce 15-step manual process to 1-click automation saving 90% time eliminating attachment errors through systematic multi-document email workflow.
