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)
| Template | To | Plain 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)
| Template | To | Plain 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)
| Template | To | Plain English |
| Ecommerce: Cart Recovery | (Default Recipients ✓) | The customer tied to the abandoned cart. |
Accounting
Accounting (account)
| Template | To | Plain 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 Notification | email_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)
| Template | To | Plain English |
| Payment Reminder | (Default Recipients ✓) | The customer contact being reminded. |
| Second reminder followup | (Default Recipients ✓) | Same. |
Bank Synchronization (account_online_synchronization)
| Template | To | Plain English |
| Bank connection expiration reminder | email_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)
| Template | To | Plain English |
| Peppol: Registration update | email_to: {{ object.account_peppol_contact_email }} | Explicit override, sends to the company's configured Peppol contact address. |
Accounting Reports (account_reports)
| Template | To | Plain 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 Deadline | partner_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)
| Template | To | Plain 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)
| Template | To | Plain English |
| Lead Forward: Send to partner | email_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)
| Template | To | Plain 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)
| Template | To | Plain English |
| Shipping: Send by Email | partner_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)
| Template | To | Plain 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)
| Template | To | Plain English |
| Project: Request Acknowledgment | (Default Recipients ✓) | The task's own customer/reporter contact. |
| Project: Task Rating Request | partner_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)
| Template | To | Plain 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)
| Template | To | Plain English |
| Document Request: Reminder | partner_to: {{ object.requestee_partner_id.id or '' }} | The specific partner the document was requested from. |
| Document: Document Request | partner_to: {{ object.requestee_partner_id.id or '' }} | Same. |
| Document: Document Share | partner_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)
| Template | To | Plain English |
| Payroll: New Declaration | email_to: {{ object.private_email or object.work_email }} | Explicit override, the employee's own personal or work address on file. |
| Payroll: New Payslip Document | email_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)
| Template | To | Plain English |
| Appointment: Appointment Booked | partner_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 Canceled | partner_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)
| Template | To | Plain English |
| Recruitment: Schedule interview | (Default Recipients ✓) | The applicant's own contact. |
Appraisals (hr_appraisal)
| Template | To | Plain 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)
| Template | To | Plain English |
| HR: Ask Feedback 360 | (none configured) | No recipient set, the calling code must supply it. |
Calendar (calendar)
| Template | To | Plain 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)
| Template | To | Plain 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 Share | email_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)
| Template | To | Plain English |
| Survey: Certification Failure | (Default Recipients ✓) | The certification attempt's own contact. |
Equity (equity)
| Template | To | Plain 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)
| Template | To | Plain 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)
| Template | To | Plain English |
| Event: Track Confirmation | (Default Recipients ✓) | The registration's own attendee contact. |
| Add reminder via email | (Default Recipients ✓) | Same. |
Gamification (gamification)
| Template | To | Plain 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)
| Template | To | Plain 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)
| Template | To | Plain English |
| Lunch: Supplier Order | partner_to: {{ ctx.get('order', {}).get('supplier_id') }} | Recipient comes entirely from context, the order's configured supplier, not a record field. |
Payroll (hr_payroll)
| Template | To | Plain English |
| Payroll: New Payslip | partner_to: {{ object.employee_id.work_contact_id.id }} | Explicit lookup, the employee's designated work contact record. |
Planning (planning)
| Template | To | Plain English |
| Planning: New Schedule | (none configured) | Nothing meaningful set on the template itself, relies on the calling code. |
| Planning: New Shift | email_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)
| Template | To | Plain English |
| Point of Sale: Receipt | (none configured) | No recipient set, the checkout flow supplies it directly. |
Point of Sale - Self-Order (pos_self_order)
| Template | To | Plain 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)
| Template | To | Plain 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)
| Template | To | Plain 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)
| Template | To | Plain English |
| Mail: Install Request | partner_to: {{ ctx['partner'].id }} | Recipient comes from context, whichever partner requested the module install. |
Settings - Portal & Sign-Up (auth_signup)
| Template | To | Plain 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)
| Template | To | Plain English |
| Settings: 2Fa Invitation | (Default Recipients ✓) | The user's own contact. |
| Settings: 2Fa New Login | (Default Recipients ✓) | Same. |
Settings - IAP Extract Notification (iap_extract)
| Template | To | Plain English |
| IAP Extract Notification | (none configured) | No recipient set. |
Surveys (survey)
| Template | To | Plain English |
| Survey: Invite | (Default Recipients ✓) | The specific respondent's own contact. |
| Survey: Certification Success | (Default Recipients ✓) | Same. |
Sustainability Reporting / CSRD (esg_csrd)
| Template | To | Plain English |
| CSRD Metric: Ask Stakeholder Reviews | (none configured) | No recipient set, matches the blank From and Subject on this same template. |
Timesheets (timesheet_grid)
| Template | To | Plain English |
| Timesheets: Approver Reminder | (Default Recipients ✓) | The employee's own contact. |
| Timesheets: Employee Reminder | (Default Recipients ✓) | Same. |
Website - AI Site Generator (website_generator)
| Template | To | Plain English |
| Website Scraped | email_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)
| Template | To | Plain English |
| Forum: Email Verification | (Default Recipients ✓) | The user's own contact. |
Developer/QA - Mail Test (test_mail)
| Template | To | Plain English |
| Mail Test Full: Tracking Template | partner_to: {{ object.customer_id.id }} | Explicit lookup on the test fixture's own customer_id field, internal test-suite content. |
| Mail Test: Template | partner_to: {{ object.customer_id.id }} | Same. |
Connectors
Amazon Connector (sale_amazon)
| Template | To | Plain English |
| Amazon: Order Synchronization Failure | email_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 Failure | email_to: {{ ctx.get('email_to') }} | Same. |
| Amazon: Available Inventory for Offers Synchronization Failure | email_to: {{ ctx.get('email_to') }} | Same. |
Gelato Connector (sale_gelato)
| Template | To | Plain 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)
| Template | To | Plain 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)
| Template | To | Plain 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)
| Template | To | Plain 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