Skip to Content

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

The From field on an Odoo email template gets all the attention because it's the one clients actually notice: wrong name in their customer's inbox, easy to spot. The To field causes fewer complaints, but only because when it's wrong, mail just goes nowhere and nobody notices until a customer calls asking where their invoice is.

Where the From field is a layered fallback chain (salesperson, then company, then whoever's logged in), the To field mostly works a different way: a single checkbox called Default Recipients. When it's on, Odoo ignores any custom recipient logic on the template and just sends to whatever contact the record itself already points to. When it's off, the template has to say explicitly who the recipient is.

Same Example: Sales: Send Quotation

Pull up the same "Sales: Send Quotation" template from the From-field side of this, flip to its Settings tab, and the recipient side looks almost anticlimactic. Default Recipients is checked, and both of the fields that could override it, email_to and partner_to, are blank.

That's the whole rule for this template: the quote goes to the customer already attached to the quote. There's no chain to walk, because there's nothing to fall back from. A quote without a customer contact isn't a real quote. Contrast that with the From side, which has to guess between three different people who could plausibly be "the sender." The To side doesn't guess. It just points at the one relationship that has to exist for the record to make sense in the first place.

Most templates on this list work exactly the same way. The interesting ones are the handful that don't, where Odoo explicitly overrides the default, or where nothing is configured at all and some other piece of code has to supply the recipient at send time.

Every Template's To 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-field breakdown: 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)

TemplateToPlain English
Sales: Send Quotation(Default Recipients ✓)The quote's own customer contact.
Sales: Send Proforma(Default Recipients ✓)Same.
Sales: Order Confirmation(Default Recipients ✓)Same.
Sales: Payment Done(Default Recipients ✓)Same.

Subscriptions (sale_subscription)

TemplateToPlain English
Subscription: Default Email Alert(Default Recipients ✓)The subscription's own customer contact.
Subscription: Payment Failure(Default Recipients ✓)Same.
Subscription: Payment Reminder(Default Recipients ✓)Same.
Subscription: Rating Request(Default Recipients ✓)Same.

eCommerce (website_sale)

TemplateToPlain English
Ecommerce: Cart Recovery(Default Recipients ✓)The customer tied to the abandoned cart.

Accounting

Accounting (account)

TemplateToPlain English
Invoice: Sending(Default Recipients ✓)The invoice's own customer contact.
Credit Note: Sending(Default Recipients ✓)The credit note's own customer contact.
Self-billing invoice: Sending(Default Recipients ✓)The invoice's own customer contact.
Self-billing credit note: Sending(Default Recipients ✓)The credit note's own customer contact.
Journal Notification(none configured)No recipient set on the template at all, the calling code must pass one in directly.
New eInvoices Notificationemail_to: {{ object.incoming_einvoice_notification_email }}Explicit override, sends to whatever address is configured in the company's e-invoice notification setting.
Payment: Payment Receipt(Default Recipients ✓)The payment's own customer contact.

Follow-up (account_followup)

TemplateToPlain English
Payment Reminder(Default Recipients ✓)The customer contact being reminded.
Second reminder followup(Default Recipients ✓)Same.

Bank Synchronization (account_online_synchronization)

TemplateToPlain English
Bank connection expiration reminderemail_to: {{ object.renewal_contact_email }}Explicit override, sends to whichever email is stored as the renewal contact for that bank connection.

Peppol E-Invoicing (account_peppol)

TemplateToPlain English
Peppol: Registration updateemail_to: {{ object.account_peppol_contact_email }}Explicit override, sends to the company's configured Peppol contact address.

Accounting Reports (account_reports)

TemplateToPlain English
Customer Statement(Default Recipients ✓)The customer contact.
Follow Up Report(Default Recipients ✓)The customer contact.
Tax payment(Default Recipients ✓)The tax return's own contact.
Tax Return Deadlinepartner_to: {{ ctx.get('partner') and ctx['partner'].id }}Recipient comes from context, whichever partner was passed in when the reminder was triggered.

CRM

Lead Mining (crm_iap_mine)

TemplateToPlain English
IAP Lead Generation Notification(none configured)No recipient set on the template, passed in directly by the calling code.

Resellers / Partner Assignment (website_crm_partner_assign)

TemplateToPlain English
Lead Forward: Send to partneremail_to: {{ ctx['partner_id'].email_formatted if ctx.get('partner_id') else ''}}The partner the lead is being forwarded to, supplied from context, not resolved from a stored field.

Purchasing

Purchase (purchase)

TemplateToPlain English
Purchase: Purchase Order(Default Recipients ✓)The purchase order's own vendor contact.
Purchase: Request For Quotation(Default Recipients ✓)Same.
Purchase: Vendor Reminder(Default Recipients ✓)Same.

Inventory

Inventory (stock)

TemplateToPlain English
Shipping: Send by Emailpartner_to: {{ object.partner_id.email and object.partner_id.id or object.partner_id.parent_id.id }}The shipment's customer contact, or their parent company's contact if the customer itself has no email on file.

Email Marketing

No `mail.template` records. Mass mailings resolve their recipient list directly on `mailing.mailing`.

Marketing Automation

No `mail.template` records. Campaign activities resolve their own target audience, not through templates.

Helpdesk

Helpdesk (helpdesk)

TemplateToPlain English
Helpdesk: Ticket Received(Default Recipients ✓)The ticket's own customer contact.
Helpdesk: Ticket Closed(Default Recipients ✓)Same.
Helpdesk: Ticket Rating Request(Default Recipients ✓)Same.

Project

Project (project)

TemplateToPlain English
Project: Request Acknowledgment(Default Recipients ✓)The task's own customer/reporter contact.
Project: Task Rating Requestpartner_to: {{ object._rating_get_partner().id }}Recipient comes from a computed lookup, whoever's tagged as the customer contact for this specific task's rating.

Field Service (industry_fsm)

TemplateToPlain English
Field Service: Field Service Report(Default Recipients ✓)The field-service task's own customer contact.
Field Service: Intervention Scheduled(Default Recipients ✓)Same.

Documents

Documents (documents)

TemplateToPlain English
Document Request: Reminderpartner_to: {{ object.requestee_partner_id.id or '' }}The specific partner the document was requested from.
Document: Document Requestpartner_to: {{ object.requestee_partner_id.id or '' }}Same.
Document: Document Sharepartner_to: {{ object.id or '' }}The recipient IS the record itself here, the model is res.partner, so this points straight at whichever partner the share was addressed to.

Payroll Documents (documents_hr_payroll)

TemplateToPlain English
Payroll: New Declarationemail_to: {{ object.private_email or object.work_email }}Explicit override, the employee's own personal or work address on file.
Payroll: New Payslip Documentemail_to: {{ object.employee_id.private_email or object.employee_id.work_email }}Same idea, one level removed since the record here is the payslip, not the employee directly.

Everything Else

Appointments (appointment)

TemplateToPlain English
Appointment: Appointment Bookedpartner_to: {{ object.partner_id.ids if (object.partner_id not in object.partner_ids) or object._skip_send_mail_status_update() else [] }}Conditional recipient logic, the booking partner is only added if they're not already counted among the event's attendees, or under a specific status-update condition.
Appointment: Appointment Canceledpartner_to: {{ (object.partner_ids + object.partner_id).ids if not object._skip_send_mail_status_update() else object.partner_id.ids }}Similarly conditional, combines the attendee list with the booking partner depending on the same status-update check.
Appointment: Attendee Invitation(Default Recipients ✓)The attendee's own contact, no override.

Appointments - Recruitment Scheduling (appointment_hr_recruitment)

TemplateToPlain English
Recruitment: Schedule interview(Default Recipients ✓)The applicant's own contact.

Appraisals (hr_appraisal)

TemplateToPlain English
HR: Appraisal Request By Employee(none configured)No recipient set, the calling code must supply it.
HR: Appraisal reminder(none configured)Same.
HR: General Appraisal Confirmation(none configured)Same.

Appraisals - 360 Feedback (hr_appraisal_survey)

TemplateToPlain English
HR: Ask Feedback 360(none configured)No recipient set, the calling code must supply it.

Calendar (calendar)

TemplateToPlain English
Calendar: Meeting Invitation(Default Recipients ✓)The attendee's own contact.
Calendar: Reminder(Default Recipients ✓)Same.
Calendar: Date Updated(Default Recipients ✓)Same.
Calendar: Event Update(Default Recipients ✓)The event's own contact (this one operates directly on calendar.event, not an attendee record).
Calendar: Event Deleted(Default Recipients ✓)Same.

eLearning (website_slides)

TemplateToPlain English
Channel Shared(none configured)No recipient set on the template, the share action supplies it directly via a wizard/context.
Elearning: Add Attendees to Course(Default Recipients ✓)The course participant's own contact.
Elearning: Course Shareemail_to: {{ ctx.get('email', '') }}Explicit override, pulled from context, whoever the share wizard was addressed to.
Elearning: Promotional Course Invitation(Default Recipients ✓)The course participant's own contact.
Elearning: Completed Course(Default Recipients ✓)Same.
Elearning: New Course Content Notification(none configured)No recipient set, supplied by the calling code.

eLearning - Certification (website_slides_survey)

TemplateToPlain English
Survey: Certification Failure(Default Recipients ✓)The certification attempt's own contact.

Equity (equity)

TemplateToPlain English
Equity Shareholder Email(Default Recipients ✓)The transaction's own contact.
Equity UBO Form Email(Default Recipients ✓)The record's own contact (a res.partner, so it's the partner itself).

Events (event)

TemplateToPlain English
Event: Registration Badge(Default Recipients ✓)The registration's own attendee contact.
Event: Registration Confirmation(Default Recipients ✓)Same.
Event: Reminder(Default Recipients ✓)Same.

Events - Website Track (website_event_track)

TemplateToPlain English
Event: Track Confirmation(Default Recipients ✓)The registration's own attendee contact.
Add reminder via email(Default Recipients ✓)Same.

Gamification (gamification)

TemplateToPlain English
Gamification: Badge Received(Default Recipients ✓)The badge recipient's own contact.
Gamification: Challenge Report(none configured)No recipient set.
Gamification: New Rank Reached(Default Recipients ✓)The user's own contact.
Gamification: Reminder For Goal Update(Default Recipients ✓)The goal's own contact.

Loyalty & Gift Cards (loyalty)

TemplateToPlain English
Coupon: Coupon Information(Default Recipients ✓)The coupon's own customer contact.
Gift Card: Gift Card Information(Default Recipients ✓)The card's own customer contact.

Lunch (lunch)

TemplateToPlain English
Lunch: Supplier Orderpartner_to: {{ ctx.get('order', {}).get('supplier_id') }}Recipient comes entirely from context, the order's configured supplier, not a record field.

Payroll (hr_payroll)

TemplateToPlain English
Payroll: New Payslippartner_to: {{ object.employee_id.work_contact_id.id }}Explicit lookup, the employee's designated work contact record.

Planning (planning)

TemplateToPlain English
Planning: New Schedule(none configured)Nothing meaningful set on the template itself, relies on the calling code.
Planning: New Shiftemail_to: {{ ctx.get('work_email', '') }}Explicit override, pulled from context, the assigned employee's work email.
Planning: Shift Re-assigned(none configured)Same as New Schedule, nothing meaningful on the template.

Point of Sale (point_of_sale)

TemplateToPlain English
Point of Sale: Receipt(none configured)No recipient set, the checkout flow supplies it directly.

Point of Sale - Self-Order (pos_self_order)

TemplateToPlain English
Takeout: Confirmation order for self(none configured)Same as the core Point of Sale receipt.
Delivery: Confirmation order for self(none configured)Same.

Recruitment (hr_recruitment)

TemplateToPlain English
Recruitment: Interest(Default Recipients ✓)The applicant's own contact.
Recruitment: Application Acknowledgement(Default Recipients ✓)Same.
Recruitment: Not interested anymore(Default Recipients ✓)Same.
Recruitment: Refuse(Default Recipients ✓)Same.

Salary Configurator (hr_contract_salary)

TemplateToPlain English
Employee: Contract And Salary Package(Default Recipients ✓)The offer's own contact.
Recruitment: Your Salary Package(Default Recipients ✓)Same.

Settings - Module Install Requests (base_install_request)

TemplateToPlain English
Mail: Install Requestpartner_to: {{ ctx['partner'].id }}Recipient comes from context, whichever partner requested the module install.

Settings - Portal & Sign-Up (auth_signup)

TemplateToPlain English
Settings: New User Invite(Default Recipients ✓)The invited user's own contact.
Settings: New Portal Sign Up(Default Recipients ✓)The new user's own contact.
Settings: New Portal User Invite(Default Recipients ✓) + email_to: {{ object.email_formatted }}Default Recipients wins over the explicit override, both happen to resolve to the same address here so it's harmless in practice, but the email_to value is technically unused.
Settings: Unregistered User Reminder(Default Recipients ✓)The user's own contact.

Settings - Two-Factor Authentication (auth_totp_mail)

TemplateToPlain English
Settings: 2Fa Invitation(Default Recipients ✓)The user's own contact.
Settings: 2Fa New Login(Default Recipients ✓)Same.

Settings - IAP Extract Notification (iap_extract)

TemplateToPlain English
IAP Extract Notification(none configured)No recipient set.

Surveys (survey)

TemplateToPlain English
Survey: Invite(Default Recipients ✓)The specific respondent's own contact.
Survey: Certification Success(Default Recipients ✓)Same.

Sustainability Reporting / CSRD (esg_csrd)

TemplateToPlain English
CSRD Metric: Ask Stakeholder Reviews(none configured)No recipient set, matches the blank From and Subject on this same template.

Timesheets (timesheet_grid)

TemplateToPlain English
Timesheets: Approver Reminder(Default Recipients ✓)The employee's own contact.
Timesheets: Employee Reminder(Default Recipients ✓)Same.

Website - AI Site Generator (website_generator)

TemplateToPlain English
Website Scrapedemail_to: {{ ctx.get('email_to') }}Recipient comes entirely from context, whoever's email was captured during the site-generation flow, the same context key this template also (unusually) reuses as its From address.

Website - Forum Profile (website_profile)

TemplateToPlain English
Forum: Email Verification(Default Recipients ✓)The user's own contact.

Developer/QA - Mail Test (test_mail)

TemplateToPlain English
Mail Test Full: Tracking Templatepartner_to: {{ object.customer_id.id }}Explicit lookup on the test fixture's own customer_id field, internal test-suite content.
Mail Test: Templatepartner_to: {{ object.customer_id.id }}Same.

Connectors

Amazon Connector (sale_amazon)

TemplateToPlain English
Amazon: Order Synchronization Failureemail_to: {{ ctx.get('email_to') }}Default Recipients is off, so this explicit override actually applies: recipient is passed in directly by the sync code, whoever's configured to receive Amazon alerts, not resolved from any record field.
Amazon: Delivery Order Synchronization Failureemail_to: {{ ctx.get('email_to') }}Same.
Amazon: Available Inventory for Offers Synchronization Failureemail_to: {{ ctx.get('email_to') }}Same.

Gelato Connector (sale_gelato)

TemplateToPlain English
Gelato: Order status update(Default Recipients ✓) + partner_to: {{ object.partner_id.email and object.partner_id.id or object.partner_id.parent_id.id }}Default Recipients wins, so the partner_to override is dead weight, sends to the order's own customer via the default resolution instead.

Lazada Connector (sale_lazada)

TemplateToPlain English
Lazada: Order Synchronization Failure(Default Recipients ✓) + email_to: {{ ctx.get('email_to') }}Dead weight, and inconsistent with its sibling connectors. Unlike Amazon (Default Recipients off, override applies), Lazada leaves Default Recipients checked, so this context-supplied address is ignored and it falls back to whatever "default recipient" resolves to on a res.users record instead. A real inconsistency between Odoo's own connector modules, not a 19 Prince customization.
Lazada: Inventory Synchronization Failure(Default Recipients ✓) + email_to: {{ ctx.get('email_to') }}Same dead-weight issue.

Shopee Connector (sale_shopee)

TemplateToPlain English
Shopee: Order Synchronization Failure(Default Recipients ✓) + email_to: {{ ctx.get('email_to') }}Same dead-weight issue as Lazada.
Shopee: Inventory Synchronization Failure(Default Recipients ✓) + email_to: {{ ctx.get('email_to') }}Same.
Shopee: Shipping Label Synchronization Failure(Default Recipients ✓) + email_to: {{ ctx.get('email_to') }}Same.

TikTok Connector (sale_tiktok)

TemplateToPlain English
TikTok: Order Synchronization Failure(Default Recipients ✓) + email_to: {{ ctx.get('email_to') }}Same dead-weight issue as Lazada and Shopee.
TikTok: Inventory Synchronization Failure(Default Recipients ✓) + email_to: {{ ctx.get('email_to') }}Same.

One inconsistency across Odoo's own connector modules is worth flagging directly. The Amazon connector turns Default Recipients off for its sync-failure templates, so the context-supplied alert address actually gets used. Lazada, Shopee, and TikTok ship the identical pattern but leave Default Recipients on, so their override never fires and mail falls back to whatever the default recipient resolution does for a system user instead. The code is the same across all four connectors, only Amazon's checkbox setting lets the override actually work. Not a 19 Prince customization, a genuine inconsistency in stock Odoo.

Two patterns are worth remembering here. First: when Default Recipients is checked, any hand-written email_to or partner_to value on the same template is dead weight. Odoo won't touch it. Second: the templates showing (none configured) aren't broken, they're just relying on whatever code fires the notification to supply the recipient directly, outside the template entirely. If you ever go looking for "where does this address come from" and the template comes up empty on all three fields, that's your signal to stop reading templates and go read the code that calls them.

— Darren from 19 Prince