The From field resolves through a fallback chain. The To field resolves through a single checkbox. The Subject field doesn't resolve through a decision at all. It's concatenation: a handful of pieces stitched together into one line, no candidates, no default recipient, just text plus text plus text. The one exception is worth pulling apart, because it's the only place in this whole system where the subject line itself runs real logic instead of just echoing a name off the record.
Same Example: Sales: Send Quotation
Here's the subject Odoo 19 ships on the standard "Sales: Send Quotation" template, the same one covered in the From and To posts:
{{ object.company_id.name }} {{ object.state in ('draft', 'sent') and 'Quotation' or 'Order' }} (Ref {{ object.name or 'n/a' }})
Read it in three pieces:
object.company_id.name: the company's name, always, first.
object.state in ('draft', 'sent') and 'Quotation' or 'Order': a live conditional. While the record is still a draft or has just been sent, the subject reads "Quotation." The moment someone confirms it into an order, the exact same template starts producing "Order" instead, with nothing edited by hand.
(Ref {{ object.name or 'n/a' }}): the record's own reference number in parentheses, falling back to "n/a" on the rare record that doesn't have one yet.
Put together, a still-open quote for Acme Corp reads "Acme Corp Quotation (Ref S00042)." Confirm that same record, and the next email about it reads "Acme Corp Order (Ref S00042)," same template, same code, different word, because the state changed underneath it. Everywhere else on this list, the subject is just pulling a name or a reference off the record. Here, it's actually branching on the record's status.
Every Template's Subject Field, Broken Down
Everything below is stock Odoo 19, pulled directly off a fresh install, no 19 Prince customization, grouped by app in the same order as the From and To breakdowns: Sales, Accounting, CRM, Purchasing, Inventory, Helpdesk, Project, Documents, everything else, then Connectors (Amazon, Lazada, Shopee, TikTok, and Gelato, grouped together at the end).
Sales
Sales (sale)
| Template | Subject | Plain English |
| Sales: Send Quotation | {{ object.company_id.name }} {{ object.state in ('draft', 'sent') and 'Quotation' or 'Order' }} (Ref {{ object.name or 'n/a' }}) | Company name + "Quotation" or "Order" depending on quote status + ref. |
| Sales: Send Proforma | {{ object.company_id.name }} {{ object.state in ('draft', 'sent') and 'Proforma' or 'Order'}} (Ref {{ object.name or 'n/a' }}) | Company name + "Proforma" or "Order" depending on quote status + ref. |
| Sales: Order Confirmation | {{ object.company_id.name }} {{ (object.get_portal_last_transaction().state == 'pending') and 'Pending Order' or 'Order' }} (Ref {{ object.name or 'n/a' }}) | Company name + "Pending Order" or "Order" depending on payment status + order ref. |
| Sales: Payment Done | {{ object.company_id.name }} {{ (object.get_portal_last_transaction().state == 'pending') and 'Pending Order' or 'Order' }} (Ref {{ object.name or 'n/a' }}) | Same pending/order logic as Order Confirmation. |
Subscriptions (sale_subscription)
| Template | Subject | Plain English |
| Subscription: Default Email Alert | {{ object.company_id.name }}: Please check the subscription {{ object.name }} | Company name + a generic prompt to check this specific subscription by name. |
| Subscription: Payment Failure | Termination of subscription {{ object.client_order_ref or object.name }} | Announces termination, naming the subscription by its customer reference or record name. |
| Subscription: Payment Reminder | Payment reminder for subscription {{ object.client_order_ref or object.name }} | Same naming pattern, framed as a reminder instead of a termination. |
| Subscription: Rating Request | {{ object.company_id.name }}: Service Rating Request | Company name + a generic service rating request. |
eCommerce (website_sale)
| Template | Subject | Plain English |
| Ecommerce: Cart Recovery | You left items in your cart! | Static text. |
Accounting
Accounting (account)
| Template | Subject | Plain English |
| Invoice: Sending | {{ object.company_id.name }} Invoice (Ref {{ object.name or 'n/a' }}) | Company name + "Invoice" + invoice number. |
| Credit Note: Sending | {{ object.company_id.name }} Credit Note (Ref {{ object.name or 'n/a' }}) | Company name + "Credit Note" + the credit note's reference number. |
| Self-billing invoice: Sending | {{ object.company_id.name }} Self-billing invoice (Ref {{ object.name or 'n/a' }}) | Company name + "Self-billing invoice" + ref. |
| Self-billing credit note: Sending | {{ object.company_id.name }} Self-billing credit note (Ref {{ object.name or 'n/a' }}) | Company name + "Self-billing credit note" + ref. |
| Journal Notification | {{ object.company_id.name }} - New invoice in {{ object.journal_id.display_name or 'Invoices' }} journal | Company name + which accounting journal the invoice landed in. |
| New eInvoices Notification | New Electronic Invoices Received | Static text. |
| Payment: Payment Receipt | {{ object.company_id.name }} Payment Receipt (Ref {{ object.name or 'n/a' }}) | Company name + "Payment Receipt" + payment reference number. |
Follow-up (account_followup)
| Template | Subject | Plain English |
| Payment Reminder | {{ (object.company_id or object._get_followup_responsible().company_id).name }} Payment Reminder - {{ object.commercial_company_name }} | Company name + "Payment Reminder" + customer's company name. |
| Second reminder followup | {{ (object.company_id or object._get_followup_responsible().company_id).name }} Payment Reminder - {{ object.commercial_company_name }} | Identical text, reused for the second-notice template. |
Worked example: two separate {{ }} blocks stitched around literal text. The first, (object.company_id or object._get_followup_responsible().company_id).name, resolves to your company's name — object.company_id if this record has one set directly, else it falls back through the same _get_followup_responsible() method covered in how the From field actually works, then reads that person's company. The second, object.commercial_company_name, is a completely different field on the same record — the customer's company name. So the subject reads "[your company] Payment Reminder - [customer's company]," assembled from two different names pulled off two different paths through the record.
Bank Synchronization (account_online_synchronization)
| Template | Subject | Plain English |
| Bank connection expiration reminder | Your bank connection is expiring soon | Static text. |
Peppol E-Invoicing (account_peppol)
| Template | Subject | Plain English |
| Peppol: Registration update | Welcome to Peppol | Static text. |
Accounting Reports (account_reports)
| Template | Subject | Plain English |
| Customer Statement | {{ (object.company_id or object._get_followup_responsible().company_id).name }} Statement - {{ object.commercial_company_name }} | Company name + "Statement" + the customer's company name. |
| Follow Up Report | {{ (object.company_id or object._get_followup_responsible().company_id).name }} Follow Up - {{ object.commercial_company_name }} | Company name + "Follow Up" + the customer's company name. |
| Tax payment | Tax return payment instructions | Static text. |
| Tax Return Deadline | Reminder: Upcoming {{ object.name }} Deadline - Action Required | Names the specific tax return + urgency framing. |
CRM
Lead Mining (crm_iap_mine)
| Template | Subject | Plain English |
| IAP Lead Generation Notification | IAP Lead Generation Notification | Static text. |
Resellers / Partner Assignment (website_crm_partner_assign)
| Template | Subject | Plain English |
| Lead Forward: Send to partner | Fwd: Lead: {{ ctx['partner_id'].name if ctx.get('partner_id') else ''}} | "Fwd: Lead:" + the target partner's name, pulled from context, blank if none was passed. |
Purchasing
Purchase (purchase)
| Template | Subject | Plain English |
| Purchase: Purchase Order | {{ object.company_id.name }} Order (Ref {{ object.name or 'n/a' }}) | Company name + "Order" + ref. |
| Purchase: Request For Quotation | {{ object.company_id.name }} Order (Ref {{ object.name or 'n/a' }}) | Identical text to the confirmed Purchase Order template, Odoo doesn't distinguish RFQ from PO in the subject the way Sales distinguishes Quotation from Order. |
| Purchase: Vendor Reminder | {{ object.company_id.name }} Order (Ref {{ object.name or 'n/a' }}) | Same subject again. |
Inventory
Inventory (stock)
| Template | Subject | Plain English |
| Shipping: Send by Email | {{ object.company_id.name }} Delivery Order (Ref {{ object.name or 'n/a' }}) | Company name + "Delivery Order" + ref. |
Email Marketing
No `mail.template` records. Mass mailings compose their subject directly on `mailing.mailing`.
Marketing Automation
No `mail.template` records. Campaign activities compose their subject directly, not through templates.
Helpdesk
Helpdesk (helpdesk)
| Template | Subject | Plain English |
| Helpdesk: Ticket Received | {{ object.name }} | Just the ticket's own name/number. |
| Helpdesk: Ticket Closed | Ticket Closed - Reference {{ object.ticket_ref if object.ticket_ref else 15 }} | "Ticket Closed - Reference" + the ticket's reference, or a literal hardcoded 15 if that field is blank, an odd fallback that looks like a leftover demo/test value in Odoo's own source. |
| Helpdesk: Ticket Rating Request | {{ object.company_id.name or object.user_id.company_id.name or 'Helpdesk' }}: Service Rating Request | Company name, or the assigned agent's company as a fallback, or literally "Helpdesk" as a last resort, + a generic service rating request. |
Project
Project (project)
| Template | Subject | Plain English |
| Project: Request Acknowledgment | Reception of {{ object.name }} | "Reception of" + the task's name. |
| Project: Task Rating Request | {{ object.project_id.company_id.name or user.env.company.name }}: Satisfaction Survey | The task's project's company name, or the current user's company as a fallback, + "Satisfaction Survey." |
Field Service (industry_fsm)
| Template | Subject | Plain English |
| Field Service: Field Service Report | Field Service Report - {{ object.name }} | "Field Service Report -" + the task name. |
| Field Service: Intervention Scheduled | Your intervention is scheduled {{ object.planned_date_begin and object.date_deadline and 'from the ' + format_datetime(object.planned_date_begin, tz=object.partner_id.tz, lang_code=object.partner_id.lang) + ' to the ' + format_datetime(object.date_deadline, tz=object.partner_id.tz, lang_code=object.partner_id.lang) or '' }} | Describes the scheduled window using the customer's own timezone and language, or renders blank if either date is missing. |
Documents
Documents (documents)
| Template | Subject | Plain English |
| Document Request: Reminder | Reminder to upload your document{{ object.name and ' : ' + object.name or '' }} | "Reminder to upload your document" + the doc's name, if it has one. |
| Document: Document Request | Document Request {{ object.name != False and ': '+ object.name or '' }} | "Document Request" + the doc's name, if it has one. |
| Document: Document Share | {{ len(ctx.get('documents', [])) }} document(s) shared with you | A count of how many documents were shared, pulled from context at send time. |
Payroll Documents (documents_hr_payroll)
| Template | Subject | Plain English |
| Payroll: New Declaration | {{ object.name }}, a new declaration file is available for you | The employee's name + a notice that a new declaration file is ready. |
| Payroll: New Payslip Document | {{ object.employee_id.name }}, a new payslip is available for you | The employee's name + a notice that a new payslip is ready. |
Everything Else
Appointments (appointment)
| Template | Subject | Plain English |
| Appointment: Appointment Booked | {{ 'Appointment Requested: ' if not object.appointment_type_id.is_always_confirm and object.appointment_status == 'request' else 'Appointment Booked: ' }}{{ object.appointment_type_id.name }} | "Appointment Requested:" or "Appointment Booked:" depending on whether the appointment type requires confirmation and its current status, + the appointment type's name. |
| Appointment: Appointment Canceled | Appointment Canceled: {{ object.appointment_type_id.name }} | "Appointment Canceled:" + the appointment type's name. |
| Appointment: Attendee Invitation | Invitation to {{ object.event_id.name }} | "Invitation to" + the calendar event's name. |
Appointments - Recruitment Scheduling (appointment_hr_recruitment)
| Template | Subject | Plain English |
| Recruitment: Schedule interview | Can we plan an interview together for your {{ (object.job_id.name + ' ') if object.job_id else '' }}application? | Asks to schedule an interview, naming the job posting if there is one. |
Appraisals (hr_appraisal)
| Template | Subject | Plain English |
| HR: Appraisal Request By Employee | {{ hasattr(object, 'name') and object.name or '' }} requests an Appraisal | The employee's own name, if the record has one, + "requests an Appraisal." |
| HR: Appraisal reminder | Appraisal Reminder | Static text. |
| HR: General Appraisal Confirmation | {{ object.employee_id.name }}: Appraisal Confirmed | The employee's name + ": Appraisal Confirmed." |
Appraisals - 360 Feedback (hr_appraisal_survey)
| Template | Subject | Plain English |
| HR: Ask Feedback 360 | Take part in {{ ctx.get('employee_id') and ctx['employee_id'].name or 'this' }} appraisal | Invites the recipient to take part in a named employee's appraisal, or generically "this" appraisal if no name came through context. |
Calendar (calendar)
| Template | Subject | Plain English |
| Calendar: Meeting Invitation | Invitation to {{ object.event_id.name }} | "Invitation to" + the event's name. |
| Calendar: Reminder | {{ object.event_id.name }} - Reminder | The event's name + " - Reminder." |
| Calendar: Date Updated | {{ object.event_id.name }}: Date updated | The event's name + ": Date updated." |
| Calendar: Event Update | {{object.name}}: Event update | The event's name + ": Event update." |
| Calendar: Event Deleted | Deleted event: {{ object.name }} | "Deleted event:" + the event's name. |
eLearning (website_slides)
| Template | Subject | Plain English |
| Channel Shared | {{ user.name }} shared a Course | The sharing user's own name + "shared a Course." |
| Elearning: Add Attendees to Course | You have been invited to join {{ object.channel_id.name }} | "You have been invited to join" + the course's name. |
| Elearning: Course Share | {{ user.name }} shared a {{ object.slide_category }} with you! | The sharing user's name + "shared a" + the content type + "with you!" |
| Elearning: Promotional Course Invitation | You have been invited to check out {{ object.channel_id.name }} | "You have been invited to check out" + the course's name. |
| Elearning: Completed Course | Congratulations! You completed {{ object.channel_id.name }} | "Congratulations! You completed" + the course's name. |
| Elearning: New Course Content Notification | New {{ object.slide_category }} published on {{ object.channel_id.name }} | "New" + the content type + "published on" + the course's name. |
eLearning - Certification (website_slides_survey)
| Template | Subject | Plain English |
| Survey: Certification Failure | You have failed the course: {{ object.slide_partner_id.channel_id.name }} | "You have failed the course:" + the course's name. |
Equity (equity)
| Template | Subject | Plain English |
| Equity Shareholder Email | Equity notice | Static text. |
| Equity UBO Form Email | UBO form | Static text. |
Events (event)
| Template | Subject | Plain English |
| Event: Registration Badge | Your badge for {{ object.event_id.name }} | "Your badge for" + the event's name. |
| Event: Registration Confirmation | Your registration at {{ object.event_id.name }} | "Your registration at" + the event's name. |
| Event: Reminder | {{ object.event_id.name }}: {{ object.event_date_range }} | The event's name + its date range. |
Events - Website Track (website_event_track)
| Template | Subject | Plain English |
| Event: Track Confirmation | Confirmation of {{ object.name }} | "Confirmation of" + the track's name. |
| Add reminder via email | Add talk reminder: {{ object.name }} | "Add talk reminder:" + the track's name. |
Gamification (gamification)
| Template | Subject | Plain English |
| Gamification: Badge Received | New badge {{ object.badge_id.name }} granted | "New badge" + the badge's name + "granted." |
| Gamification: Challenge Report | (blank) | No Subject configured either, this is another unconfigured-as-shipped template. |
| Gamification: New Rank Reached | New rank: {{ object.rank_id.name }} | "New rank:" + the rank's name. |
| Gamification: Reminder For Goal Update | (blank) | No Subject configured. |
Loyalty & Gift Cards (loyalty)
| Template | Subject | Plain English |
| Coupon: Coupon Information | Your reward coupon from {{ object.program_id.company_id.name }} | "Your reward coupon from" + the program's owning company name. |
| Gift Card: Gift Card Information | Your Gift Card at {{ object.company_id.name }} | "Your Gift Card at" + company name. |
Lunch (lunch)
| Template | Subject | Plain English |
| Lunch: Supplier Order | Orders for {{ ctx.get('order', {}).get('company_name') }} | "Orders for" + the company name, pulled from context at send time rather than the record. |
Payroll (hr_payroll)
| Template | Subject | Plain English |
| Payroll: New Payslip | {{ object.employee_id.name }}, a new payslip is available for you | Same wording pattern as the Documents app's payslip template. |
Planning (planning)
| Template | Subject | Plain English |
| Planning: New Schedule | Your planning from {{ format_date(ctx.get('start_datetime')) }} to {{ format_date(ctx.get('end_datetime')) }} | "Your planning from" + a formatted start date + "to" + a formatted end date, both pulled from context. |
| Planning: New Shift | {{ ctx.get('mail_subject', '') }} {{ ctx.get('start_datetime' , '') }} | The entire subject is built from context values, nothing hardcoded in the template itself. |
| Planning: Shift Re-assigned | Your shift on {{ ctx.get('start_datetime') }} was re-assigned to {{ ctx.get('new_assignee_name', 'Marc Demo') }} | "Your shift on" + a context start-datetime + "was re-assigned to" + the new assignee's name, or a literal demo placeholder name ("Marc Demo") if that context value is missing. |
Point of Sale (point_of_sale)
| Template | Subject | Plain English |
| Point of Sale: Receipt | Your {{ object.config_id.name }} receipt | "Your" + the POS register's name + "receipt." |
Point of Sale - Self-Order (pos_self_order)
| Template | Subject | Plain English |
| Takeout: Confirmation order for self | Your {{ object.config_id.name }} receipt | Identical to the core Point of Sale receipt subject. |
| Delivery: Confirmation order for self | Your {{ object.config_id.name }} receipt | Same. |
Recruitment (hr_recruitment)
| Template | Subject | Plain English |
| Recruitment: Interest | Your Job Application: {{ object.job_id.name }} | "Your Job Application:" + the job posting's name. |
| Recruitment: Application Acknowledgement | Your Job Application: {{ object.job_id.name }} | Identical text. |
| Recruitment: Not interested anymore | Your Job Application: {{ object.job_id.name }} | Identical text. |
| Recruitment: Refuse | Your Job Application: {{ object.job_id.name }} | Identical text, all four Recruitment stage templates share the same subject line. |
Salary Configurator (hr_contract_salary)
| Template | Subject | Plain English |
| Employee: Contract And Salary Package | {{ object.company_id.name }}: Job Offer - {{ object.employee_id.name }} | Company name + "Job Offer -" + the employee's name. |
| Recruitment: Your Salary Package | {{ object.company_id.name }}: Job Offer - {{ object.applicant_id.partner_name }} | Same shape, the applicant-facing version, naming the applicant instead of the employee. |
Settings - Module Install Requests (base_install_request)
| Template | Subject | Plain English |
| Mail: Install Request | Module Activation Request for "{{ object.module_id.shortdesc }}" | Names the specific app/module being requested. |
Settings - Portal & Sign-Up (auth_signup)
| Template | Subject | Plain English |
| Settings: New User Invite | {{ object.create_uid.name }} from {{ object.company_id.name }} invites you to connect to Odoo | Names the specific person who sent the invite + company name. |
| Settings: New Portal Sign Up | Welcome to {{ object.company_id.name }}! | Company name welcome message. |
| Settings: New Portal User Invite | Your account at {{ object.company_id.name }} | Company name + generic account notice. |
| Settings: Unregistered User Reminder | Reminder for unregistered users | Static text. |
Settings - Two-Factor Authentication (auth_totp_mail)
| Template | Subject | Plain English |
| Settings: 2Fa Invitation | Invitation to activate two-factor authentication on your Odoo account | Static text. |
| Settings: 2Fa New Login | Your two-factor authentication code | Static text. |
Settings - IAP Extract Notification (iap_extract)
| Template | Subject | Plain English |
| IAP Extract Notification | IAP Extract Notification | Static text. |
Surveys (survey)
| Template | Subject | Plain English |
| Survey: Invite | Participate to {{ object.survey_id.display_name }} survey | "Participate to" + the survey's display name + "survey." |
| Survey: Certification Success | Certification: {{ object.survey_id.display_name }} | "Certification:" + the survey's display name. |
Sustainability Reporting / CSRD (esg_csrd)
| Template | Subject | Plain English |
| CSRD Metric: Ask Stakeholder Reviews | (blank) | No Subject configured, matches the blank From on this same template. |
Timesheets (timesheet_grid)
| Template | Subject | Plain English |
| Timesheets: Approver Reminder | Reminder to validate your team's timesheets | Static text. |
| Timesheets: Employee Reminder | Reminder to log your timesheets | Static text. |
Website - AI Site Generator (website_generator)
| Template | Subject | Plain English |
| Website Scraped | Your Odoo Website is Ready! | Static text. |
Website - Forum Profile (website_profile)
| Template | Subject | Plain English |
| Forum: Email Verification | {{ object.company_id.name }} Profile validation | Company name + "Profile validation." |
Developer/QA - Mail Test (test_mail)
| Template | Subject | Plain English |
| Mail Test Full: Tracking Template | Test Template | Static text, internal test-suite fixture. |
| Mail Test: Template | Post on {{ object.name }} | "Post on" + the record's name, internal test-suite fixture. |
Connectors
Amazon Connector (sale_amazon)
| Template | Subject | Plain English |
| Amazon: Order Synchronization Failure | Synchronization of Amazon order {{ ctx.get('amazon_order_ref') }} has failed | Names the specific failed Amazon order by its own reference. |
| Amazon: Delivery Order Synchronization Failure | Synchronization of delivery orders has failed | Static text. |
| Amazon: Available Inventory for Offers Synchronization Failure | Synchronization of Amazon Stock has failed | Static text. |
Gelato Connector (sale_gelato)
| Template | Subject | Plain English |
| Gelato: Order status update | {{ object.reference }} | Just the order's own reference number, nothing else. |
Lazada Connector (sale_lazada)
| Template | Subject | Plain English |
| Lazada: Order Synchronization Failure | Synchronization of Lazada order {{ ctx.get('lazada_order_ref') }} has failed | Names the specific failed Lazada order. |
| Lazada: Inventory Synchronization Failure | Synchronization of Lazada inventory has failed | Static text. |
Shopee Connector (sale_shopee)
| Template | Subject | Plain English |
| Shopee: Order Synchronization Failure | Synchronization of Shopee order {{ ctx.get('shopee_order_ref') }} has failed | Names the specific failed Shopee order. |
| Shopee: Inventory Synchronization Failure | Synchronization of Shopee inventory has failed | Static text. |
| Shopee: Shipping Label Synchronization Failure | Synchronization of shipping labels has failed | Static text. |
TikTok Connector (sale_tiktok)
| Template | Subject | Plain English |
| TikTok: Order Synchronization Failure | Synchronization of TikTok Shop order {{ ctx.get('tiktok_order_ref') }} has failed | Names the specific failed TikTok order. |
| TikTok: Inventory Synchronization Failure | Synchronization of TikTok Shop inventory has failed | Static text. |
Most of these rows are static or a straight name-swap, nothing to debug. The one pattern worth carrying with you: whenever a subject line seems to change wording on its own, on the same record, with no one touching the template, go looking for a conditional like the one on the quotation template. Odoo isn't reacting to an edit. It's just running a check against the record's current state, every single time it renders.
— Darren from 19 Prince