Every Odoo project I've run eventually hits the same moment: a client asks why an invoice or a quote showed up in their customer's inbox from the wrong person. Not a bounce, not a spam filter. The email arrived fine, it just said "From: Dave" when Dave had nothing to do with it. Nobody configured that. Odoo just decided.
The reason is that Odoo doesn't treat "who sent this email" as one field you fill in. It's a fallback chain: a short list of candidates, checked top to bottom, and the first one with an actual email address wins. Untangling which candidate wins on which template is most of the work of getting Odoo's outbound email right for a client.
Example - Sales: Send Quotation
Here's the actual expression Odoo 19 ships on the standard "Sales: Send Quotation" template, the one that fires when someone clicks Send on a quote:
{{ (object.user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }}
Read left to right, that's three checks, in order:
object.user_id.email_formatted: is there a salesperson assigned to this quote, and do they have an email on file? If yes, they're the sender.
object.company_id.email_formatted: if not, does the company itself have an email address configured? If yes, the customer sees the company brand as sender instead of a person.
user.email_formatted: if neither of those resolved to anything, fall back to whoever is actually logged in and clicking Send.
The "or" here doesn't mean "any of these." It means first-match-wins. Odoo checks candidate one, and only moves to candidate two if candidate one comes back empty. That's the entire mental model for reading any of these expressions: read it as a waterfall, not a set.
That same shape (salesperson, then company, then logged-in user, with small variations) repeats across almost every customer-facing template Odoo ships. A few skip a step. One template runs the order backwards. None of it is arbitrary once you know to look for the chain.
Every Template's From Field, Broken Down
Everything below is stock Odoo 19, pulled directly off a fresh install, no 19 Prince customization, grouped by app: 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 | Send From | Plain English |
| Sales: Send Quotation | {{ (object.user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }} | Assigned salesperson first, then company brand, then logged-in user. |
| Sales: Send Proforma | {{ (object.user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }} | Assigned salesperson first, then company brand, then logged-in user. |
| Sales: Order Confirmation | {{ (object.user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }} | Assigned salesperson first, then company brand, then logged-in user. |
| Sales: Payment Done | {{ (object.user_id.email_formatted or user.email_formatted) }} | Assigned salesperson first, then logged-in user, skips the company-brand fallback. |
Subscriptions (sale_subscription)
| Template | Send From | Plain English |
| Subscription: Default Email Alert | {{ (object.user_id.email_formatted or user.email_formatted) }} | Assigned salesperson first, then logged-in user. |
| Subscription: Payment Failure | {{ (object.user_id.email_formatted or object.company_id.email or user.email_formatted) }} | Assigned salesperson first, then the company's raw email field (not the formatted display version), then logged-in user. |
| Subscription: Payment Reminder | {{ (object.user_id.email_formatted or object.company_id.email or user.email_formatted) }} | Same as Payment Failure. |
| Subscription: Rating Request | {{ (object._rating_get_operator().email_formatted or user.email_formatted) }} | Whoever's computed as the record's rating "operator" (the rep tied to it), else logged-in user. |
eCommerce (website_sale)
| Template | Send From | Plain English |
| Ecommerce: Cart Recovery | {{ (object.user_id.email_formatted or object.company_id.email_formatted or user.email_formatted or '') }} | Assigned salesperson first, then company, then logged-in user, else blank. |
Accounting
Accounting (account)
| Template | Send From | Plain English |
| Invoice: Sending | {{ (object.invoice_user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }} | Invoice's salesperson first, then company brand, then logged-in user. |
| Credit Note: Sending | {{ (object.invoice_user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }} | Same. |
| Self-billing invoice: Sending | {{ (object.invoice_user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }} | Same. |
| Self-billing credit note: Sending | {{ (object.invoice_user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }} | Same. |
| Journal Notification | {{ object.invoice_user_id.email_formatted or object.company_id.email_formatted or user.email_formatted }} | Same 3-way fallback, just written without the outer parentheses. |
| New eInvoices Notification | {{ object.company_id.email_formatted }} | Always the company brand identity, no fallback. |
| Payment: Payment Receipt | (blank) | No From set at all. |
Follow-up (account_followup)
| Template | Send From | Plain English |
| Payment Reminder | {{ object._get_followup_responsible().email_formatted }} | Whoever is assigned as this customer's collections/follow-up contact. |
| Second reminder followup | {{ object._get_followup_responsible().email_formatted }} | Same. |
Worked example: unlike the field chains above, the Follow-up templates use a method call instead of a simple field lookup — {{ object._get_followup_responsible().email_formatted }}. Three pieces, left to right: object is the customer record the reminder is about; _get_followup_responsible() is a method, not a stored field, so Odoo runs actual logic to work out who owns collections for that customer; .email_formatted formats whatever comes back as "Name <email>", the same as every other From field on this page. Notably, there's no or fallback chain here — if the method can't resolve anyone, the From field just comes back empty.
Bank Synchronization (account_online_synchronization)
| Template | Send From | Plain English |
| Bank connection expiration reminder | {{ object.company_id.email_formatted or user.email_formatted }} | Company brand first, then whoever's logged in. |
Peppol E-Invoicing (account_peppol)
| Template | Send From | Plain English |
| Peppol: Registration update | {{ object.email_formatted }} | The record here is the company itself, so this is just the company's own address, no fallback. |
Accounting Reports (account_reports)
| Template | Send From | Plain English |
| Customer Statement | {{ object._get_followup_responsible().email_formatted }} | The customer's collections/follow-up contact. |
| Follow Up Report | {{ object._get_followup_responsible().email_formatted }} | Same. |
| Tax payment | {{ (user.email_formatted or object.company_id.email_formatted) }} | Logged-in user first, then company brand, the one template in the whole database that reverses the usual priority. |
| Tax Return Deadline | (blank) | No From set at all. |
CRM
Lead Mining (crm_iap_mine)
| Template | Send From | Plain English |
| IAP Lead Generation Notification | [email protected] | Hardcoded Odoo address, not client-branded at all. |
Resellers / Partner Assignment (website_crm_partner_assign)
| Template | Send From | Plain English |
| Lead Forward: Send to partner | {{ user.email_formatted }} | Whoever is logged in and forwarding the lead, no fallback at all. Notably, this is the exact "quote creator is the sender" pattern proposed as a custom fix for a client's sender-attribution complaint, Odoo already ships it here, just on this one CRM lead-forwarding template rather than on the main Sales quotation flow. |
Purchasing
Purchase (purchase)
| Template | Send From | Plain English |
| Purchase: Vendor Reminder | {{ (object.user_id.email_formatted or user.email_formatted) }} | Assigned buyer first, then logged-in user. |
| Purchase: Purchase Order | (blank) | No From set at all. |
| Purchase: Request For Quotation | (blank) | No From set at all. |
Inventory
Inventory (stock)
| Template | Send From | Plain English |
| Shipping: Send by Email | (blank) | No From set at all. |
Email Marketing
No `mail.template` records. Mass mailings compose their body/sender directly on `mailing.mailing`.
Marketing Automation
No `mail.template` records. Campaign activities compose their content directly, not through templates.
Helpdesk
Helpdesk (helpdesk)
| Template | Send From | Plain English |
| Helpdesk: Ticket Received | {{ (object.team_id.alias_email_from or object.company_id.email_formatted or object.user_id.email_formatted or user.email_formatted) }} | The helpdesk team's alias address first, then company brand, then the assigned agent, then logged-in user. Four-deep, the longest fallback chain in the database. |
| Helpdesk: Ticket Closed | {{ (object.team_id.alias_email_from or object.company_id.email_formatted or object.user_id.email_formatted or user.email_formatted) }} | Same 4-way chain. |
| Helpdesk: Ticket Rating Request | {{ (object.team_id.alias_email_from or object.company_id.email_formatted or object._rating_get_operator().email_formatted or user.email_formatted) }} | Same shape, but the third step is a computed rating-operator lookup instead of the assigned agent field directly. |
Project
Project (project)
| Template | Send From | Plain English |
| Project: Request Acknowledgment | (blank) | No From set at all. |
| Project: Task Rating Request | {{ (object._rating_get_operator().email_formatted if object._rating_get_operator() else user.email_formatted) }} | Whoever's computed as the task's rating operator, if one exists, else logged-in user. |
Field Service (industry_fsm)
| Template | Send From | Plain English |
| Field Service: Field Service Report | (blank) | No From set at all. |
| Field Service: Intervention Scheduled | (blank) | No From set at all. |
Documents
Documents (documents)
| Template | Send From | Plain English |
| Document Request: Reminder | (blank) | No From set at all. |
| Document: Document Request | (blank) | No From set at all. |
| Document: Document Share | (blank) | No From set at all. |
Payroll Documents (documents_hr_payroll)
| Template | Send From | Plain English |
| Payroll: New Declaration | {{ user.email_formatted }} | Whoever is logged in, no fallback, the HR staffer processing payroll. |
| Payroll: New Payslip Document | {{ user.email_formatted }} | Same. |
Everything Else
Appointments (appointment)
| Template | Send From | Plain English |
| Appointment: Appointment Booked | {{ (user.email_formatted or object.user_id.email_formatted) }} | Logged-in user first, then the event's assigned user, a reversed order compared to most other templates. |
| Appointment: Appointment Canceled | {{ (user.email_formatted or object.user_id.email_formatted) }} | Same reversed order. |
| Appointment: Attendee Invitation | {{ (object.event_id.user_id.email_formatted or user.email_formatted or '') }} | The calendar event's organizer first, then logged-in user, else blank. |
Appointments - Recruitment Scheduling (appointment_hr_recruitment)
| Template | Send From | Plain English |
| Recruitment: Schedule interview | (blank) | No From set at all. |
Appraisals (hr_appraisal)
| Template | Send From | Plain English |
| HR: Appraisal Request By Employee | (blank) | No From set at all. |
| HR: Appraisal reminder | (blank) | No From set at all. |
| HR: General Appraisal Confirmation | (blank) | No From set at all. |
Appraisals - 360 Feedback (hr_appraisal_survey)
| Template | Send From | Plain English |
| HR: Ask Feedback 360 | (blank) | No From set at all. |
Calendar (calendar)
| Template | Send From | Plain English |
| Calendar: Meeting Invitation | {{ (object.event_id.user_id.email_formatted or user.email_formatted or '') }} | The event's organizer first, then logged-in user, else blank. |
| Calendar: Reminder | {{ (object.event_id.user_id.email_formatted or user.email_formatted or '') }} | Same. |
| Calendar: Date Updated | {{ (object.event_id.user_id.email_formatted or user.email_formatted or '') }} | Same. |
| Calendar: Event Update | {{ (object.user_id.email_formatted or user.email_formatted or '') }} | Same shape, but operates directly on the event record instead of an attendee record. |
| Calendar: Event Deleted | {{ (object.user_id.email_formatted or user.email_formatted or '') }} | Same. |
eLearning (website_slides)
| Template | Send From | Plain English |
| Channel Shared | {{ user.email_formatted }} | Logged-in user, no fallback. |
| Elearning: Add Attendees to Course | {{ user.email_formatted }} | Same. |
| Elearning: Course Share | {{ user.email_formatted }} | Same. |
| Elearning: Promotional Course Invitation | {{ user.email_formatted }} | Same. |
| Elearning: Completed Course | {{ (object.channel_id.user_id.email_formatted or object.channel_id.user_id.company_id.catchall_formatted) }} | The course's instructor/owner first, then that instructor's company catchall address, no logged-in-user fallback at all. |
| Elearning: New Course Content Notification | (blank) | No From set at all. |
eLearning - Certification (website_slides_survey)
| Template | Send From | Plain English |
| Survey: Certification Failure | (blank) | No From set at all. |
Equity (equity)
| Template | Send From | Plain English |
| Equity Shareholder Email | (blank) | No From set at all. |
| Equity UBO Form Email | (blank) | No From set at all. |
Events (event)
| Template | Send From | Plain English |
| Event: Registration Badge | {{ (object.event_id.organizer_id.email_formatted or object.event_id.company_id.email_formatted or user.email_formatted or '') }} | Event organizer first, then the event's company brand, then logged-in user, else blank. |
| Event: Registration Confirmation | {{ (object.event_id.organizer_id.email_formatted or object.event_id.company_id.email_formatted or user.email_formatted or '') }} | Same. |
| Event: Reminder | {{ (object.event_id.organizer_id.email_formatted or object.event_id.company_id.email_formatted or user.email_formatted or '') }} | Same. |
Events - Website Track (website_event_track)
| Template | Send From | Plain English |
| Event: Track Confirmation | {{ (object.event_id.organizer_id.email_formatted or object.event_id.company_id.email_formatted or user.email_formatted or '') }} | Same organizer/company/logged-in chain as the core Events app. |
| Add reminder via email | {{ (object.event_id.organizer_id.email_formatted or object.event_id.company_id.email_formatted or user.email_formatted or '') }} | Same. |
Gamification (gamification)
| Template | Send From | Plain English |
| Gamification: Badge Received | (blank) | No From set at all. |
| Gamification: Challenge Report | (blank) | No From set at all. |
| Gamification: New Rank Reached | (blank) | No From set at all. |
| Gamification: Reminder For Goal Update | (blank) | No From set at all. |
Loyalty & Gift Cards (loyalty)
| Template | Send From | Plain English |
| Coupon: Coupon Information | {{ object.program_id.company_id.email }} | The loyalty program's owning company, its raw email field, not the formatted display version. |
| Gift Card: Gift Card Information | (blank) | No From set at all. |
Lunch (lunch)
| Template | Send From | Plain English |
| Lunch: Supplier Order | {{ ctx.get('order', {}).get('email_from') }} | Pulled entirely from context at send time (whoever placed the order), not from any field on the record. |
Payroll (hr_payroll)
| Template | Send From | Plain English |
| Payroll: New Payslip | {{ user.email_formatted }} | Logged-in user, no fallback. |
Planning (planning)
| Template | Send From | Plain English |
| Planning: New Schedule | {{ object.company_id.email_formatted }} | Always the company brand, no fallback. |
| Planning: New Shift | {{ object.company_id.email_formatted }} | Same. |
| Planning: Shift Re-assigned | {{ object.company_id.email_formatted }} | Same. |
Point of Sale (point_of_sale)
| Template | Send From | Plain English |
| Point of Sale: Receipt | {{ (object.company_id.email_formatted or object.user_id.email_formatted)}} | Company brand first, then the assigned cashier/user. |
Point of Sale - Self-Order (pos_self_order)
| Template | Send From | Plain English |
| Takeout: Confirmation order for self | {{ (object.company_id.email_formatted or object.user_id.email_formatted)}} | Same as the core Point of Sale receipt. |
| Delivery: Confirmation order for self | {{ (object.company_id.email_formatted or object.user_id.email_formatted)}} | Same. |
Recruitment (hr_recruitment)
| Template | Send From | Plain English |
| Recruitment: Interest | (blank) | No From set at all. |
| Recruitment: Application Acknowledgement | (blank) | No From set at all. |
| Recruitment: Not interested anymore | (blank) | No From set at all. |
| Recruitment: Refuse | (blank) | No From set at all. |
Salary Configurator (hr_contract_salary)
| Template | Send From | Plain English |
| Employee: Contract And Salary Package | (blank) | No From set at all. |
| Recruitment: Your Salary Package | (blank) | No From set at all. |
Settings - Module Install Requests (base_install_request)
| Template | Send From | Plain English |
| Mail: Install Request | {{ object.user_id.email_formatted or user.email_formatted }} | The person who requested the module install, else whoever's logged in. |
Settings - Portal & Sign-Up (auth_signup)
| Template | Send From | Plain English |
| Settings: New User Invite | {{ (object.company_id.email_formatted or user.email_formatted) }} | Company brand first, then whoever's logged in. |
| Settings: New Portal Sign Up | {{ (object.company_id.email_formatted or user.email_formatted) }} | Same. |
| Settings: New Portal User Invite | {{ (object.company_id.email_formatted or user.email_formatted) }} | Same. |
| Settings: Unregistered User Reminder | {{ (object.company_id.email_formatted or user.email_formatted) }} | Same. |
Settings - Two-Factor Authentication (auth_totp_mail)
| Template | Send From | Plain English |
| Settings: 2Fa Invitation | {{ (object.company_id.email_formatted or user.email_formatted) }} | Company brand first, then whoever's logged in. |
| Settings: 2Fa New Login | {{ (object.company_id.email_formatted or user.email_formatted) }} | Same. |
Settings - IAP Extract Notification (iap_extract)
| Template | Send From | Plain English |
| IAP Extract Notification | [email protected] | Hardcoded Odoo address, not client-branded at all. |
Surveys (survey)
| Template | Send From | Plain English |
| Survey: Invite | {{ user.email_formatted }} | Logged-in user, no fallback. |
| Survey: Certification Success | {{ (object.survey_id.create_uid.email_formatted or user.email_formatted or user.company_id.catchall_formatted) }} | The survey's creator first, then logged-in user, then the company's catchall address as a last resort, three deep, ending in a mailbox instead of a person. |
Sustainability Reporting / CSRD (esg_csrd)
| Template | Send From | Plain English |
| CSRD Metric: Ask Stakeholder Reviews | (blank) | No From, and no Subject either, this template is essentially an unconfigured placeholder as shipped. |
Timesheets (timesheet_grid)
| Template | Send From | Plain English |
| Timesheets: Approver Reminder | {{ (object.user_id.company_id.partner_id.email_formatted or user.email_formatted) }} | The employee's own company contact record first, then logged-in user. |
| Timesheets: Employee Reminder | {{ (object.user_id.company_id.partner_id.email_formatted or user.email_formatted) }} | Same. |
Website - AI Site Generator (website_generator)
| Template | Send From | Plain English |
| Website Scraped | {{ ctx.get('email_to') }} | Pulled from context at send time, notably reusing the same context key normally meant for the recipient address as the sender too. |
Website - Forum Profile (website_profile)
| Template | Send From | Plain English |
| Forum: Email Verification | {{ object.company_id.email_formatted or object.email_formatted }} | Company brand first, then falls back to the record's own address, since the record here is the user being verified, not the more usual "logged-in user" fallback. |
Developer/QA - Mail Test (test_mail)
| Template | Send From | Plain English |
| Mail Test Full: Tracking Template | (blank) | No From set. These two are Odoo's own internal test-suite fixtures, not real business templates. |
| Mail Test: Template | (blank) | Same. |
Connectors
Amazon Connector (sale_amazon)
| Template | Send From | Plain English |
| Amazon: Order Synchronization Failure | {{ (object.company_id.email or object.user_id.email_formatted or user.email_formatted) }} | Company's raw email field first, then the assigned user's email, then logged-in user. |
| Amazon: Delivery Order Synchronization Failure | {{ (object.company_id.email or object.user_id.email_formatted or user.email_formatted) }} | Same fallback order. |
| Amazon: Available Inventory for Offers Synchronization Failure | {{ (object.company_id.email or object.user_id.email_formatted or user.email_formatted) }} | Same fallback order. |
Gelato Connector (sale_gelato)
| Template | Send From | Plain English |
| Gelato: Order status update | (blank) | No From set at all, Odoo falls back to the system/alias-domain default. |
Lazada Connector (sale_lazada)
| Template | Send From | Plain English |
| Lazada: Order Synchronization Failure | {{ (object.company_id.email or object.user_id.email_formatted or user.email_formatted) }} | Same 3-way pattern as the Amazon connector. |
| Lazada: Inventory Synchronization Failure | {{ (object.company_id.email or object.user_id.email_formatted or user.email_formatted) }} | Same. |
Shopee Connector (sale_shopee)
| Template | Send From | Plain English |
| Shopee: Order Synchronization Failure | {{ (object.company_id.email or object.user_id.email_formatted or user.email_formatted) }} | Same 3-way pattern as the other connectors. |
| Shopee: Inventory Synchronization Failure | {{ (object.company_id.email or object.user_id.email_formatted or user.email_formatted) }} | Same. |
| Shopee: Shipping Label Synchronization Failure | {{ (object.company_id.email or object.user_id.email_formatted or user.email_formatted) }} | Same. |
TikTok Connector (sale_tiktok)
| Template | Send From | Plain English |
| TikTok: Order Synchronization Failure | {{ (object.company_id.email or object.user_id.email_formatted or user.email_formatted) }} | Same 3-way pattern as the other connectors. |
| TikTok: Inventory Synchronization Failure | {{ (object.company_id.email or object.user_id.email_formatted or user.email_formatted) }} | Same. |
One row in the CRM section is worth calling out on its own. The "Lead Forward: Send to partner" template sends from whoever is logged in and forwarding the lead, with no fallback to the company or anyone else. That's the exact "quote creator is the sender" pattern that comes up whenever a client complains about the wrong name showing up in a customer's inbox. Odoo already ships that fix, it just lives on one CRM lead-forwarding template, not on the Sales quotation flow where most people go looking for it.
If your customers have ever asked "why did this come from someone who doesn't work here anymore," the answer is almost always sitting in one of these rows. Fixing it isn't a rebuild. It's usually a one-line change to the right template, once you know which candidate in the chain is actually winning.
— Darren from 19 Prince