Skip to Content

Odoo 19 Email Templates: How the 'Subject' Field Actually Works

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:

  1. object.company_id.name: the company's name, always, first.
  2. 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.
  3. (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)

TemplateSubjectPlain 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)

TemplateSubjectPlain 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 FailureTermination of subscription {{ object.client_order_ref or object.name }}Announces termination, naming the subscription by its customer reference or record name.
Subscription: Payment ReminderPayment 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 RequestCompany name + a generic service rating request.

eCommerce (website_sale)

TemplateSubjectPlain English
Ecommerce: Cart RecoveryYou left items in your cart!Static text.

Accounting

Accounting (account)

TemplateSubjectPlain 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' }} journalCompany name + which accounting journal the invoice landed in.
New eInvoices NotificationNew Electronic Invoices ReceivedStatic 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)

TemplateSubjectPlain 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)

TemplateSubjectPlain English
Bank connection expiration reminderYour bank connection is expiring soonStatic text.

Peppol E-Invoicing (account_peppol)

TemplateSubjectPlain English
Peppol: Registration updateWelcome to PeppolStatic text.

Accounting Reports (account_reports)

TemplateSubjectPlain 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 paymentTax return payment instructionsStatic text.
Tax Return DeadlineReminder: Upcoming {{ object.name }} Deadline - Action RequiredNames the specific tax return + urgency framing.

CRM

Lead Mining (crm_iap_mine)

TemplateSubjectPlain English
IAP Lead Generation NotificationIAP Lead Generation NotificationStatic text.

Resellers / Partner Assignment (website_crm_partner_assign)

TemplateSubjectPlain English
Lead Forward: Send to partnerFwd: 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)

TemplateSubjectPlain 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)

TemplateSubjectPlain 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)

TemplateSubjectPlain English
Helpdesk: Ticket Received{{ object.name }}Just the ticket's own name/number.
Helpdesk: Ticket ClosedTicket 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 RequestCompany 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)

TemplateSubjectPlain English
Project: Request AcknowledgmentReception 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 SurveyThe task's project's company name, or the current user's company as a fallback, + "Satisfaction Survey."

Field Service (industry_fsm)

TemplateSubjectPlain English
Field Service: Field Service ReportField Service Report - {{ object.name }}"Field Service Report -" + the task name.
Field Service: Intervention ScheduledYour 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)

TemplateSubjectPlain English
Document Request: ReminderReminder 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 RequestDocument 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 youA count of how many documents were shared, pulled from context at send time.

Payroll Documents (documents_hr_payroll)

TemplateSubjectPlain English
Payroll: New Declaration{{ object.name }}, a new declaration file is available for youThe 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 youThe employee's name + a notice that a new payslip is ready.

Everything Else

Appointments (appointment)

TemplateSubjectPlain 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 CanceledAppointment Canceled: {{ object.appointment_type_id.name }}"Appointment Canceled:" + the appointment type's name.
Appointment: Attendee InvitationInvitation to {{ object.event_id.name }}"Invitation to" + the calendar event's name.

Appointments - Recruitment Scheduling (appointment_hr_recruitment)

TemplateSubjectPlain English
Recruitment: Schedule interviewCan 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)

TemplateSubjectPlain English
HR: Appraisal Request By Employee{{ hasattr(object, 'name') and object.name or '' }} requests an AppraisalThe employee's own name, if the record has one, + "requests an Appraisal."
HR: Appraisal reminderAppraisal ReminderStatic text.
HR: General Appraisal Confirmation{{ object.employee_id.name }}: Appraisal ConfirmedThe employee's name + ": Appraisal Confirmed."

Appraisals - 360 Feedback (hr_appraisal_survey)

TemplateSubjectPlain English
HR: Ask Feedback 360Take part in {{ ctx.get('employee_id') and ctx['employee_id'].name or 'this' }} appraisalInvites the recipient to take part in a named employee's appraisal, or generically "this" appraisal if no name came through context.

Calendar (calendar)

TemplateSubjectPlain English
Calendar: Meeting InvitationInvitation to {{ object.event_id.name }}"Invitation to" + the event's name.
Calendar: Reminder{{ object.event_id.name }} - ReminderThe event's name + " - Reminder."
Calendar: Date Updated{{ object.event_id.name }}: Date updatedThe event's name + ": Date updated."
Calendar: Event Update{{object.name}}: Event updateThe event's name + ": Event update."
Calendar: Event DeletedDeleted event: {{ object.name }}"Deleted event:" + the event's name.

eLearning (website_slides)

TemplateSubjectPlain English
Channel Shared{{ user.name }} shared a CourseThe sharing user's own name + "shared a Course."
Elearning: Add Attendees to CourseYou 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 InvitationYou have been invited to check out {{ object.channel_id.name }}"You have been invited to check out" + the course's name.
Elearning: Completed CourseCongratulations! You completed {{ object.channel_id.name }}"Congratulations! You completed" + the course's name.
Elearning: New Course Content NotificationNew {{ object.slide_category }} published on {{ object.channel_id.name }}"New" + the content type + "published on" + the course's name.

eLearning - Certification (website_slides_survey)

TemplateSubjectPlain English
Survey: Certification FailureYou have failed the course: {{ object.slide_partner_id.channel_id.name }}"You have failed the course:" + the course's name.

Equity (equity)

TemplateSubjectPlain English
Equity Shareholder EmailEquity noticeStatic text.
Equity UBO Form EmailUBO formStatic text.

Events (event)

TemplateSubjectPlain English
Event: Registration BadgeYour badge for {{ object.event_id.name }}"Your badge for" + the event's name.
Event: Registration ConfirmationYour 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)

TemplateSubjectPlain English
Event: Track ConfirmationConfirmation of {{ object.name }}"Confirmation of" + the track's name.
Add reminder via emailAdd talk reminder: {{ object.name }}"Add talk reminder:" + the track's name.

Gamification (gamification)

TemplateSubjectPlain English
Gamification: Badge ReceivedNew 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 ReachedNew rank: {{ object.rank_id.name }}"New rank:" + the rank's name.
Gamification: Reminder For Goal Update(blank)No Subject configured.

Loyalty & Gift Cards (loyalty)

TemplateSubjectPlain English
Coupon: Coupon InformationYour reward coupon from {{ object.program_id.company_id.name }} "Your reward coupon from" + the program's owning company name.
Gift Card: Gift Card InformationYour Gift Card at {{ object.company_id.name }}"Your Gift Card at" + company name.

Lunch (lunch)

TemplateSubjectPlain English
Lunch: Supplier OrderOrders 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)

TemplateSubjectPlain English
Payroll: New Payslip{{ object.employee_id.name }}, a new payslip is available for youSame wording pattern as the Documents app's payslip template.

Planning (planning)

TemplateSubjectPlain English
Planning: New ScheduleYour 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-assignedYour 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)

TemplateSubjectPlain English
Point of Sale: ReceiptYour {{ object.config_id.name }} receipt"Your" + the POS register's name + "receipt."

Point of Sale - Self-Order (pos_self_order)

TemplateSubjectPlain English
Takeout: Confirmation order for selfYour {{ object.config_id.name }} receiptIdentical to the core Point of Sale receipt subject.
Delivery: Confirmation order for selfYour {{ object.config_id.name }} receiptSame.

Recruitment (hr_recruitment)

TemplateSubjectPlain English
Recruitment: InterestYour Job Application: {{ object.job_id.name }}"Your Job Application:" + the job posting's name.
Recruitment: Application AcknowledgementYour Job Application: {{ object.job_id.name }}Identical text.
Recruitment: Not interested anymoreYour Job Application: {{ object.job_id.name }}Identical text.
Recruitment: RefuseYour Job Application: {{ object.job_id.name }}Identical text, all four Recruitment stage templates share the same subject line.

Salary Configurator (hr_contract_salary)

TemplateSubjectPlain 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)

TemplateSubjectPlain English
Mail: Install RequestModule Activation Request for "{{ object.module_id.shortdesc }}"Names the specific app/module being requested.

Settings - Portal & Sign-Up (auth_signup)

TemplateSubjectPlain English
Settings: New User Invite{{ object.create_uid.name }} from {{ object.company_id.name }} invites you to connect to OdooNames the specific person who sent the invite + company name.
Settings: New Portal Sign UpWelcome to {{ object.company_id.name }}!Company name welcome message.
Settings: New Portal User InviteYour account at {{ object.company_id.name }}Company name + generic account notice.
Settings: Unregistered User ReminderReminder for unregistered usersStatic text.

Settings - Two-Factor Authentication (auth_totp_mail)

TemplateSubjectPlain English
Settings: 2Fa InvitationInvitation to activate two-factor authentication on your Odoo accountStatic text.
Settings: 2Fa New LoginYour two-factor authentication codeStatic text.

Settings - IAP Extract Notification (iap_extract)

TemplateSubjectPlain English
IAP Extract NotificationIAP Extract NotificationStatic text.

Surveys (survey)

TemplateSubjectPlain English
Survey: InviteParticipate to {{ object.survey_id.display_name }} survey"Participate to" + the survey's display name + "survey."
Survey: Certification SuccessCertification: {{ object.survey_id.display_name }}"Certification:" + the survey's display name.

Sustainability Reporting / CSRD (esg_csrd)

TemplateSubjectPlain English
CSRD Metric: Ask Stakeholder Reviews(blank)No Subject configured, matches the blank From on this same template.

Timesheets (timesheet_grid)

TemplateSubjectPlain English
Timesheets: Approver ReminderReminder to validate your team's timesheetsStatic text.
Timesheets: Employee ReminderReminder to log your timesheetsStatic text.

Website - AI Site Generator (website_generator)

TemplateSubjectPlain English
Website ScrapedYour Odoo Website is Ready!Static text.

Website - Forum Profile (website_profile)

TemplateSubjectPlain English
Forum: Email Verification{{ object.company_id.name }} Profile validationCompany name + "Profile validation."

Developer/QA - Mail Test (test_mail)

TemplateSubjectPlain English
Mail Test Full: Tracking TemplateTest TemplateStatic text, internal test-suite fixture.
Mail Test: TemplatePost on {{ object.name }}"Post on" + the record's name, internal test-suite fixture.

Connectors

Amazon Connector (sale_amazon)

TemplateSubjectPlain English
Amazon: Order Synchronization FailureSynchronization of Amazon order {{ ctx.get('amazon_order_ref') }} has failedNames the specific failed Amazon order by its own reference.
Amazon: Delivery Order Synchronization FailureSynchronization of delivery orders has failedStatic text.
Amazon: Available Inventory for Offers Synchronization FailureSynchronization of Amazon Stock has failedStatic text.

Gelato Connector (sale_gelato)

TemplateSubjectPlain English
Gelato: Order status update{{ object.reference }}Just the order's own reference number, nothing else.

Lazada Connector (sale_lazada)

TemplateSubjectPlain English
Lazada: Order Synchronization FailureSynchronization of Lazada order {{ ctx.get('lazada_order_ref') }} has failedNames the specific failed Lazada order.
Lazada: Inventory Synchronization FailureSynchronization of Lazada inventory has failedStatic text.

Shopee Connector (sale_shopee)

TemplateSubjectPlain English
Shopee: Order Synchronization FailureSynchronization of Shopee order {{ ctx.get('shopee_order_ref') }} has failedNames the specific failed Shopee order.
Shopee: Inventory Synchronization FailureSynchronization of Shopee inventory has failedStatic text.
Shopee: Shipping Label Synchronization FailureSynchronization of shipping labels has failedStatic text.

TikTok Connector (sale_tiktok)

TemplateSubjectPlain English
TikTok: Order Synchronization FailureSynchronization of TikTok Shop order {{ ctx.get('tiktok_order_ref') }} has failedNames the specific failed TikTok order.
TikTok: Inventory Synchronization FailureSynchronization of TikTok Shop inventory has failedStatic 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