Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions mail_user_outgoing_server/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
=========================
Mail User Outgoing Server
=========================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:5586a102ca9eb179e77c5be519a2989899c2c12f6b9d7b841497e16343969220
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fmail-lightgray.png?logo=github
:target: https://github.com/OCA/mail/tree/18.0/mail_user_outgoing_server
:alt: OCA/mail
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/mail-18-0/mail-18-0-mail_user_outgoing_server
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/mail&target_branch=18.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

This module lets **each user connect their own Outlook mailbox** as a
personal outgoing mail server, directly from their own preferences,
without an administrator having to do it on their behalf.

In stock Odoo 18 the Outlook OAuth flow (opening the Microsoft login and
the OAuth callback) is restricted to ``base.group_system`` users, so a
regular user cannot authenticate their own mailbox. This module:

- Adds an **Outgoing Mail Server** selector on *My Preferences*
(``Default`` / ``Outlook``). Picking ``Outlook`` creates the user's
personal outgoing mail server and starts the OAuth flow in the user's
own session.
- Adds ``owner_user_id`` on ``ir.mail_server`` to tie a personal server
to a single user, and keeps those personal servers out of the generic
/ fallback server selection, so a personal mailbox is only ever used
to send its owner's own email (matched by the ``From`` address).
- Lets the **owner** of a personal server (a non-admin user) run the
Outlook OAuth flow and complete the callback, activating the server
once the login succeeds.

..

**Note:** this is a backport. The feature is available **natively
from Odoo 19.0**, so this module only makes sense on 18.0. See the
*Roadmap* section.

**Table of contents**

.. contents::
:local:

Configuration
=============

The administrator only has to set things up once:

1. Go to *Settings → General Settings → Discuss* and enable **Use Custom
Email Servers**, then enable **Support Outlook Authentication** and
fill in the Outlook **Client ID** and **Client Secret** of your
registered Azure application.

2. In the Azure app registration:

- Add the redirect URI
``<your-odoo-base-url>/microsoft_outlook/confirm``.
- Add the delegated permission **Office 365 Exchange Online →
``SMTP.Send``** and grant **admin consent** for the tenant, so
every user can authenticate without being blocked.

3. In Exchange Online, make sure **Authenticated SMTP** (SMTP AUTH) is
enabled for each mailbox that will send through Odoo (it is often
disabled at tenant level):

::

Set-CASMailbox -Identity user@example.com -SmtpClientAuthenticationDisabled $false

Internal (non-share) users can then connect their own mailbox themselves
(see *Usage*).

Usage
=====

As an internal user:

1. Open the user menu (top-right avatar) → **My Preferences**.
2. In the **Preferences** tab, find the **Outgoing Mail Server**
selector and choose **Outlook**.
3. You are redirected to the Microsoft login in your own session.
Authenticate with your own Outlook / Office 365 account.
4. You are redirected back to your preferences with the server
connected. Use **Test Connection** to verify it.

From then on, the email you send whose ``From`` is your own address is
delivered through your personal Outlook mailbox. Your personal server is
never used to send anyone else's email.

To stop using it, select **Default** again; the personal server is
removed.

Known issues / Roadmap
======================

- This module is a **backport**. The per-user outgoing mail server
feature is available **natively from Odoo 19.0** (in the ``mail`` /
``microsoft_outlook`` core modules). Therefore this module is meant
for **18.0 only** and should **not** be migrated to 19.0 or later — on
those versions the functionality is already provided by Odoo.
- Only **Outlook** is supported. Gmail OAuth (covered natively in 19.0
too) is out of scope here.
- Compared to the 19.0 implementation, the following refinements are
intentionally **not** backported, as they are not required for the
feature to work on 18.0:

- the per-author candidate-set routing in ``mail.mail`` (on 18.0
outgoing mail is already routed by the exact ``from_filter`` match
in ``ir.mail_server._find_mail_server``);
- the per-minute anti-spam throttling of personal mail servers;
- the post-OAuth check that the Microsoft login email matches the
server email (18.0's OAuth scope does not return an ``id_token``;
the server's ``from_filter`` is still locked to the user's address
and Microsoft enforces send-as on its side).

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/mail/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/mail/issues/new?body=module:%20mail_user_outgoing_server%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
-------

* ForgeFlow

Contributors
------------

- Ricard Calvo <ricard.calvo@forgeflow.com>
- ForgeFlow S.L.
<`https://www.forgeflow.com\\> <https://www.forgeflow.com\>>`__

Maintainers
-----------

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

This module is part of the `OCA/mail <https://github.com/OCA/mail/tree/18.0/mail_user_outgoing_server>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
4 changes: 4 additions & 0 deletions mail_user_outgoing_server/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright 2026 ForgeFlow S.L. (https://www.forgeflow.com)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import models
from . import controllers
33 changes: 33 additions & 0 deletions mail_user_outgoing_server/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Copyright 2026 ForgeFlow S.L. (https://www.forgeflow.com)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
"name": "Mail User Outgoing Server",
"version": "18.0.1.0.0",
"category": "Discuss",
"summary": "Let each user connect their own Outlook mailbox as a personal "
"outgoing mail server from their preferences",
"license": "AGPL-3",
"author": "ForgeFlow, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/mail",
"development_status": "Beta",
"maintainers": [],
"depends": ["mail", "microsoft_outlook"],
"data": [
"views/res_users_views.xml",
],
"assets": {
"web.assets_backend": [
"mail_user_outgoing_server/static/src/views/fields/"
"mail_server_configurator_selection/"
"mail_server_configurator_selection.esm.js",
"mail_user_outgoing_server/static/src/views/fields/"
"mail_server_configurator_selection/"
"mail_server_configurator_selection.xml",
"mail_user_outgoing_server/static/src/views/fields/"
"mail_server_configurator_selection/"
"mail_server_configurator_selection.scss",
],
},
"installable": True,
"application": False,
}
3 changes: 3 additions & 0 deletions mail_user_outgoing_server/controllers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Copyright 2026 ForgeFlow S.L. (https://www.forgeflow.com)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import main
103 changes: 103 additions & 0 deletions mail_user_outgoing_server/controllers/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Copyright 2026 ForgeFlow S.L. (https://www.forgeflow.com)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
"""Override the Outlook OAuth callback to support personal mail servers.

The v18 core callback (a) only allows ``base.group_system`` users and (b) does
not activate the record. For a personal server connected by a regular user we
must instead authorize the *owner* of the server and un-archive it once the
tokens have been fetched.
"""

import json
import logging

from werkzeug.exceptions import Forbidden

from odoo import http
from odoo.exceptions import UserError
from odoo.http import request
from odoo.tools import consteq

from odoo.addons.microsoft_outlook.controllers.main import (
MicrosoftOutlookController,
)

_logger = logging.getLogger(__name__)


class MicrosoftOutlookPersonalController(MicrosoftOutlookController):
@http.route("/microsoft_outlook/confirm", type="http", auth="user")
def microsoft_outlook_callback(
self, code=None, state=None, error_description=None, **kwargs
):
try:
state = json.loads(state)
model_name = state["model"]
rec_id = state["id"]
csrf_token = state["csrf_token"]
except Exception:
_logger.error("Microsoft Outlook: Wrong state value %r.", state)
raise Forbidden() from None

model = request.env[model_name]
if not isinstance(model, request.env.registry["microsoft.outlook.mixin"]):
raise Forbidden()

record = model.browse(rec_id).sudo().exists()
if not record:
raise Forbidden()

# Authorize: system administrators, or the owner of a personal server.
is_owner = (
record._name == "ir.mail_server"
and record.owner_user_id.id == request.env.user.id
)
if not request.env.user.has_group("base.group_system") and not is_owner:
_logger.error(
"Microsoft Outlook: %s is not allowed to link %s#%s.",
request.env.user.login,
model_name,
rec_id,
)
raise Forbidden()

if not csrf_token or not consteq(csrf_token, record._get_outlook_csrf_token()):
_logger.error("Microsoft Outlook: Wrong CSRF token during authentication.")
raise Forbidden()

if error_description:
return request.render(
"microsoft_outlook.microsoft_outlook_oauth_error",
{
"error": error_description,
"model_name": model_name,
"rec_id": rec_id,
},
)

try:
refresh_token, access_token, expiration = (
record._fetch_outlook_refresh_token(code)
)
except UserError as e:
return request.render(
"microsoft_outlook.microsoft_outlook_oauth_error",
{
"error": str(e),
"model_name": model_name,
"rec_id": rec_id,
},
)

record.write(
{
"active": True,
"microsoft_outlook_refresh_token": refresh_token,
"microsoft_outlook_access_token": access_token,
"microsoft_outlook_access_token_expiration": expiration,
}
)

if is_owner:
return request.redirect(f"/odoo/my-preferences/{request.env.user.id}")
return request.redirect(f"/odoo/{model_name}/{rec_id}")
4 changes: 4 additions & 0 deletions mail_user_outgoing_server/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright 2026 ForgeFlow S.L. (https://www.forgeflow.com)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import ir_mail_server
from . import res_users
Loading
Loading