Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
2197698
[18.0][MIG] website_mass_mailing_double_opt_in: Migration to v18
hitesh-erpharbor Jan 16, 2026
00b86d9
[18.0][IMP] Imlemented the code to fixed the CI tests
hitesh-erpharbor Jan 16, 2026
181730a
[18.0][IMP] Changed the module description
hitesh-erpharbor Jan 19, 2026
be0085c
[18.0][REM] Removed README folder
hitesh-erpharbor Jan 19, 2026
d094d30
Simplify module summary in __manifest__.py
jans23 Jan 19, 2026
a8bba59
[18.0][IMP] website_mass_mailing_double_opt_in: Impplemented the tran…
hitesh-erpharbor Jan 22, 2026
9902b52
update German translation
jans23 Jan 22, 2026
aac6155
[18.0][IMP] website_mass_mailing_double_opt_in: Implemented the contr…
hitesh-erpharbor Jan 28, 2026
e7f9c2d
[18.0][IMP] website_mass_mailing_double_opt_in: Implmented the confir…
hitesh-erpharbor Feb 19, 2026
ac280f0
[18.0][IMP] website_mass_mailing_double_opt_in: Fix the CI tests
hitesh-erpharbor Feb 19, 2026
b0e7772
[18.0][IMP] website_mass_mailing_double_opt_in: Implemented email tem…
hitesh-erpharbor Feb 20, 2026
aa0c495
[18.0][IMP] website_mass_mailing_double_opt_in: Implemented the index…
hitesh-erpharbor Feb 20, 2026
585f2b0
[18.0][IMP] website_mass_mailing_double_opt_in: Implemented PO file a…
hitesh-erpharbor Feb 25, 2026
c56717a
Update author information in manifest file
jans23 Feb 26, 2026
5d7846b
[18.0][IMP] website_mass_mailing_double_opt_in_nitrokey: Implemented …
hitesh-erpharbor Mar 26, 2026
19aec53
[18.0][IMP] website_mass_mailing_double_opt_in_nitrokey: Implemented …
hitesh-erpharbor Mar 26, 2026
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
21 changes: 21 additions & 0 deletions website_mass_mailing_double_opt_in/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
==================================
Website Mass Mailing Double opt-in
==================================

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

To configure this module, you need to:

#. Go to **Website > Edit Page > Select Any Newsletter Template > Save**

Usage
=====

This module extends Odoo's website mass mailing capabilities by implementing
a double opt-in subscription process for newsletter signups.

When visitors subscribe to a newsletter through the website, they must confirm
their subscription via a confirmation email before being added to the mailing
list. This two-step verification process helps ensure legitimate subscriptions
and compliance with email marketing regulations (like GDPR)
3 changes: 3 additions & 0 deletions website_mass_mailing_double_opt_in/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from . import controllers, models
32 changes: 32 additions & 0 deletions website_mass_mailing_double_opt_in/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

{
"name": "Website Mass Mailing Double opt-in",
"version": "18.0.1.0.0",
"category": "Website",
"author": "Nitrokey GmbH",
"website": "https://github.com/nitrokey/odoo-modules",
"license": "AGPL-3",
"summary": """
This module extends Odoo's website mass mailing capabilities by implementing
a double opt-in subscription process for newsletter signups.
""",
"depends": [
"website_mass_mailing",
],
"data": [
"security/mass_mailing_security.xml",
"security/ir.model.access.csv",
"data/mail_template.xml",
"views/mass_mailing_view.xml",
"views/invalid_confirmation.xml",
"views/subscribe_template.xml",
],
"assets": {
"web.assets_tests": [
"website_mass_mailing_double_opt_in/static/tests/**/*",
"website_mass_mailing_double_opt_in/static/tests/tours/**/*",
],
},
"installable": True,
}
3 changes: 3 additions & 0 deletions website_mass_mailing_double_opt_in/controllers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from . import main
142 changes: 142 additions & 0 deletions website_mass_mailing_double_opt_in/controllers/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

import logging

from odoo import _
from odoo.http import Controller, request, route

from odoo.addons.mass_mailing.controllers.main import MassMailController

_logger = logging.getLogger(__name__)


class MassMailController(MassMailController):
@route("/website_mass_mailing/subscribe", type="json", website=True, auth="public")
def subscribe(self, list_id, value, subscription_type, **post):
if not request.env["ir.http"]._verify_request_recaptcha_token(
"website_mass_mailing_subscribe"
):
return {
"toast_type": "danger",
"toast_content": _("Suspicious activity detected by Google reCaptcha."),
}

fname = self._get_fname(subscription_type)
# Customisation Start
if subscription_type == "email":
subscription = request.env["mailing.subscription"].sudo()
# add email to session
request.session["mass_mailing_email"] = (
subscription.double_opt_in_subscribe(
list_id,
value,
language=post.get("language") or request.lang.code,
)
)
else:
self.subscribe_to_newsletter(subscription_type, value, list_id, fname)
# Customisation End
return {
"toast_type": "success",
"toast_content": _("Thanks for subscribing!"),
}


class ConsentController(Controller):
def consent_success(self):
"""Successful consent to redirect to different sides if required"""
base_url = request.httprequest.host_url.rstrip("/")
redirect_url = base_url + "/subscribed"
return request.redirect(redirect_url)

def consent_failure(self):
"""Redirect to a public invalid page"""
return request.redirect("/newsletter/invalid")

@route("/newsletter/invalid", type="http", auth="public", website=True)
def invalid_page(self, **kwargs):
"""Public route for invalid page (uses website layout)"""
return request.render(
"website_mass_mailing_double_opt_in.invalid_subscription_confirmation_template"
)

@route("/subscribed", type="http", auth="public", website=True)
def subscribed(self, **kwargs):
return request.render("website_mass_mailing_double_opt_in.subscribe_newsletter")

def _prepare_mail_content(self, mailing_list_contact, language):
"""Newsletter Subscribed email template content"""
if language == "de_DE":
# Get translated strings
greeting = "Hallo,"
subject = "Sie haben den Newsletter abonniert"
thank_you = "vielen Dank für die Anmeldung zum Newsletter."
best_regards = "Viele Grüße,"
team = "Ihr Team"
elif language == "en_US":
greeting = "Hi!"
subject = "You Have Subscribed to the Newsletter"
thank_you = "Thank you for subscribing the newsletter."
best_regards = "Best regards,"
team = "your team"
else:
# Get translated strings
greeting = _("Hi!")
subject = _("You Have Subscribed to the Newsletter")
thank_you = _("Thank you for subscribing the newsletter.")
best_regards = _("Best regards,")
team = _("your team")
mail_values = {
"subject": subject,
"body_html": f"""
<div>
<p>{greeting}</p>
<p>{thank_you}</p>
<br />
<p>{best_regards}<br />{team}</p>
</div>
""",
"email_from": request.env.user.partner_id.email,
"email_to": mailing_list_contact.contact_id.email,
"state": "outgoing",
}
return mail_values

@route(
"/newsletter/confirmation/<access_token>",
type="http",
auth="none",
website=True,
)
def consent(self, access_token, **kwargs):
try:
mailing_list_contact = (
request.env["mailing.subscription"]
.sudo()
.search([("access_token", "=", access_token)], limit=1)
)
if not mailing_list_contact:
_logger.warning(
"No mailing subscription found for access token: %s", access_token
)
return self.consent_failure()

mailing_list_contact.write({"opt_out": False})

# Send email with explicit user context for template rendering
language = (
mailing_list_contact.mail_language or request.lang.code or "en_US"
)
if not request.env.user:
request.env.user = mailing_list_contact.create_uid
try:
mail_values = self._prepare_mail_content(mailing_list_contact, language)
mail = request.env["mail.mail"].sudo().create(mail_values)
mail.with_context(**{"lang": language}).sudo().send()
except Exception as e:
_logger.warning("Send mail issue: %s", e)
return self.consent_success()

except Exception as e:
_logger.error("Error processing newsletter confirmation: %s", str(e))
return self.consent_failure()
62 changes: 62 additions & 0 deletions website_mass_mailing_double_opt_in/data/mail_template.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<!-- Confirm Subscription Email Template -->
<record id="newsletter_confirmation_request_template" model="mail.template">
<field name="name">Newsletter Subscription</field>
<field name="subject">Confirm your newsletter subscription</field>
<field name="model_id" ref="mass_mailing.model_mailing_subscription" />
<field name="email_to">{{ object.contact_id.email }}</field>
<field name="body_html" type="html">
<div style="margin: -10px -10px; padding:50px 30px 50px 30px; height:100%;">
<div style="margin:0 auto; max-width:660px;">
<div
style="float: left; background-color: #FFFFFF; padding:10px 30px 10px 30px; border: 1px solid #DDDDDD;"
>
<div style="float: left; max-width:470px;">
<p
style="line-height: 21px; font-family: Helvetica, Verdana, Arial, sans-serif; font-size: 12px;"
>
<strong
style="line-height: 21px; font-family: Helvetica, Verdana, Arial, sans-serif; font-size: 18px;"
>Confirm your newsletter subscription
</strong>
</p>
<div
style="line-height: 21px; min-height: 100px; font-family: Helvetica, Verdana, Arial, sans-serif; font-size: 12px;"
>
<p
style="line-height: 21px; font-family: Helvetica, Verdana, Arial, sans-serif; font-size: 12px;"
>Thanks for subscribing to our email list.
</p>
<p
style="line-height: 21px; font-family: Helvetica, Verdana, Arial, sans-serif; font-size: 12px;"
>You allow to be in touch with you via email for the purpose of news, updates
and product information. If you wish to withdraw your consent and stop hearing from
us, simply click the unsubscribe link at the bottom of every email. By subscribing,
you agree that we may process your information in accordance with our data privacy policy.
</p>
<p
style="line-height: 21px; font-family: Helvetica, Verdana, Arial, sans-serif; font-size: 12px;"
>Please confirm your subscription by clicking the button below:
</p>
<p
style="line-height: 21px; font-family: Helvetica, Verdana, Arial, sans-serif; font-size: 12px; margin-bottom: 25px; padding: 15px; text-align: center;"
>
<a
t-attf-href="/newsletter/confirmation/{{ object.access_token }}"
style="background-color: #449d44; padding: 12px; font-weight: 12px; text-decoration: none; color: #fff; border-radius: 5px; font-size:16px;"
>Confirm
</a>
</p>
<p
style="line-height: 21px; font-family: Helvetica, Verdana, Arial, sans-serif; font-size: 12px;"
>Thank you,<br />
</p>
</div>
</div>
</div>
</div>
</div>
</field>
</record>
</odoo>
Loading
Loading