Initial commit: Mail packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:51 +02:00
commit 4e53507711
1948 changed files with 751201 additions and 0 deletions

View file

@ -0,0 +1,52 @@
# Email Marketing
Odoo addon: mass_mailing
## Installation
```bash
pip install odoo-bringout-oca-ocb-mass_mailing
```
## Dependencies
This addon depends on:
- contacts
- mail
- utm
- link_tracker
- web_editor
- web_kanban_gauge
- social_media
- web_tour
- digest
## Manifest Information
- **Name**: Email Marketing
- **Version**: 2.5
- **Category**: Marketing/Email Marketing
- **License**: LGPL-3
- **Installable**: False
## Source
Based on [OCA/OCB](https://github.com/OCA/OCB) branch 16.0, addon `mass_mailing`.
## License
This package maintains the original LGPL-3 license from the upstream Odoo project.
## Documentation
- Overview: doc/OVERVIEW.md
- Architecture: doc/ARCHITECTURE.md
- Models: doc/MODELS.md
- Controllers: doc/CONTROLLERS.md
- Wizards: doc/WIZARDS.md
- Install: doc/INSTALL.md
- Usage: doc/USAGE.md
- Configuration: doc/CONFIGURATION.md
- Dependencies: doc/DEPENDENCIES.md
- Troubleshooting: doc/TROUBLESHOOTING.md
- FAQ: doc/FAQ.md

View file

@ -0,0 +1,32 @@
# Architecture
```mermaid
flowchart TD
U[Users] -->|HTTP| V[Views and QWeb Templates]
V --> C[Controllers]
V --> W[Wizards Transient Models]
C --> M[Models and ORM]
W --> M
M --> R[Reports]
DX[Data XML] --> M
S[Security ACLs and Groups] -. enforces .-> M
subgraph Mass_mailing Module - mass_mailing
direction LR
M:::layer
W:::layer
C:::layer
V:::layer
R:::layer
S:::layer
DX:::layer
end
classDef layer fill:#eef8ff,stroke:#6ea8fe,stroke-width:1px
```
Notes
- Views include tree/form/kanban templates and report templates.
- Controllers provide website/portal routes when present.
- Wizards are UI flows implemented with `models.TransientModel`.
- Data XML loads data/demo records; Security defines groups and access.

View file

@ -0,0 +1,3 @@
# Configuration
Refer to Odoo settings for mass_mailing. Configure related models, access rights, and options as needed.

View file

@ -0,0 +1,17 @@
# Controllers
HTTP routes provided by this module.
```mermaid
sequenceDiagram
participant U as User/Client
participant C as Module Controllers
participant O as ORM/Views
U->>C: HTTP GET/POST (routes)
C->>O: ORM operations, render templates
O-->>U: HTML/JSON/PDF
```
Notes
- See files in controllers/ for route definitions.

View file

@ -0,0 +1,13 @@
# Dependencies
This addon depends on:
- [contacts](../../odoo-bringout-oca-ocb-contacts)
- [mail](../../odoo-bringout-oca-ocb-mail)
- [utm](../../odoo-bringout-oca-ocb-utm)
- [link_tracker](../../odoo-bringout-oca-ocb-link_tracker)
- [web_editor](../../odoo-bringout-oca-ocb-web_editor)
- [web_kanban_gauge](../../odoo-bringout-oca-ocb-web_kanban_gauge)
- [social_media](../../odoo-bringout-oca-ocb-social_media)
- [web_tour](../../odoo-bringout-oca-ocb-web_tour)
- [digest](../../odoo-bringout-oca-ocb-digest)

View file

@ -0,0 +1,4 @@
# FAQ
- Q: Which Odoo version? A: 16.0 (OCA/OCB packaged).
- Q: How to enable? A: Start server with --addon mass_mailing or install in UI.

View file

@ -0,0 +1,7 @@
# Install
```bash
pip install odoo-bringout-oca-ocb-mass_mailing"
# or
uv pip install odoo-bringout-oca-ocb-mass_mailing"
```

View file

@ -0,0 +1,31 @@
# Models
Detected core models and extensions in mass_mailing.
```mermaid
classDiagram
class ir_mail_server
class mailing_contact
class mailing_contact_subscription
class mailing_filter
class mailing_list
class mailing_mailing
class mailing_trace
class res_users
class ir_http
class ir_model
class link_tracker
class link_tracker_click
class mail_render_mixin
class mail_thread
class res_company
class res_config_settings
class res_partner
class utm_campaign
class utm_medium
class utm_source
```
Notes
- Classes show model technical names; fields omitted for brevity.
- Items listed under _inherit are extensions of existing models.

View file

@ -0,0 +1,6 @@
# Overview
Packaged Odoo addon: mass_mailing. Provides features documented in upstream Odoo 16 under this addon.
- Source: OCA/OCB 16.0, addon mass_mailing
- License: LGPL-3

View file

@ -0,0 +1,26 @@
# Reports
Report definitions and templates in mass_mailing.
```mermaid
classDiagram
class MailingTraceReport
Model <|-- MailingTraceReport
```
## Available Reports
No named reports found in XML files.
## Report Files
- **__init__.py** (Python logic)
- **mailing_trace_report.py** (Python logic)
- **mailing_trace_report_views.xml** (XML template/definition)
## Notes
- Named reports above are accessible through Odoo's reporting menu
- Python files define report logic and data processing
- XML files contain report templates, definitions, and formatting
- Reports are integrated with Odoo's printing and email systems

View file

@ -0,0 +1,42 @@
# Security
Access control and security definitions in mass_mailing.
## Access Control Lists (ACLs)
Model access permissions defined in:
- **[ir.model.access.csv](../mass_mailing/security/ir.model.access.csv)**
- 24 model access rules
## Record Rules
Row-level security rules defined in:
## Security Groups & Configuration
Security groups and permissions defined in:
- **[res_groups_data.xml](../mass_mailing/security/res_groups_data.xml)**
- 3 security groups defined
```mermaid
graph TB
subgraph "Security Layers"
A[Users] --> B[Groups]
B --> C[Access Control Lists]
C --> D[Models]
B --> E[Record Rules]
E --> F[Individual Records]
end
```
Security files overview:
- **[ir.model.access.csv](../mass_mailing/security/ir.model.access.csv)**
- Model access permissions (CRUD rights)
- **[res_groups_data.xml](../mass_mailing/security/res_groups_data.xml)**
- Security groups, categories, and XML-based rules
Notes
- Access Control Lists define which groups can access which models
- Record Rules provide row-level security (filter records by user/group)
- Security groups organize users and define permission sets
- All security is enforced at the ORM level by Odoo

View file

@ -0,0 +1,5 @@
# Troubleshooting
- Ensure Python and Odoo environment matches repo guidance.
- Check database connectivity and logs if startup fails.
- Validate that dependent addons listed in DEPENDENCIES.md are installed.

View file

@ -0,0 +1,7 @@
# Usage
Start Odoo including this addon (from repo root):
```bash
python3 scripts/odoo_web_server.py --db-name mydb --addon mass_mailing
```

View file

@ -0,0 +1,13 @@
# Wizards
Transient models exposed as UI wizards in mass_mailing.
```mermaid
classDiagram
class MailComposeMessage
class MailingContactImport
class MailingContactToList
class MailingMailingScheduleDate
class MassMailingListMerge
class TestMassMailing
```

View file

@ -0,0 +1,97 @@
Odoo Mass Mailing
-----------------
Easily send mass mailing to your leads, opportunities or customers
with Odoo <a href="https://www.odoo.com/app/email-marketing">Email Marketing</a>. Track
marketing campaigns performance to improve conversion rates. Design
professional emails and reuse templates in a few clicks.
Send Professional Emails
------------------------
Import database of prospects or filter on existing leads, opportunities and
customers in just a few clicks.
Define email templates to reuse content or specific design for your newsletter.
Setup several email servers with their own IP/domain to optimise opening rates.
Organize Marketing Campaigns
----------------------------
Design, Send, Track by Campaigns with our <a href="https://www.odoo.com/app/email-marketing">Lead Automation</a> app.
Get real time statistics on campaigns performance to improve your conversion
rate. Track mails sent, received, opened and answered.
Easily manage your marketing campaigns, discussion groups, leads and
opportunities in one simple and powerful platform.
Integrated with Odoo Apps
-------------------------
Get access to mass mailing features from every Odoo app to improve the way your
users communicate.
Send template of emails from Odoo <a href="https://www.odoo.com/app/email-marketing">CRM opportunities</a>, select leads based
on marketing segments, send <a href="https://www.odoo.com/app/recruitment">job offers</a> and automate
answers to applicants, reuse email template in the lead automation marketing
campaigns.
Answers to your emails appears automatically in the history of every document
with the social network module.
Clean Your Lead Database
------------------------
Get a clean lead database that improves over the time using the performance of
your mails. Odoo handle bounce mails efficiently, flag erroneous leads
accordingly and gives you statistics on the quality of your leads.
One click emails send
---------------------
The marketing department will love working on campaigns. But you can also give
a one click mass mailing facility to all others users on their own prospects or
documents.
Select a few documents (e.g. leads, support tickets, suppliers, applicants,
...) and send emails to their contacts in one click, reusing existing emails
templates.
Follow-up On Answers
--------------------
The chatter feature enables you to communicate faster and more efficiently with
your customer. Get documents created automatically (leads, opportunities,
tasks, ...) based on answers to your mass mailing campaigns Follow the
discussion directly on the business documents within Odoo or via email.
Get all the negotiations and discussions attached to the right document and
relevent managers notified on specific events.
Campaigns Dashboard
-------------------
Get the insights you need to make smarter marketing campaign. Track statistics
per campaign: bounce rates, sent mails, best content, etc. The clear dashboards
gives you a direct overview of your campaign performance.
Fully Integrated With Others Apps
---------------------------------
Define automated actions (e.g. ask a salesperson to call, send an email, ...)
based on triggers (no activity since 20 days, answered a promotional email,
etc.)
Optimize campaigns from lead to close, on every channel. Make smarter decisions
about where to invest and show the impact of your marketing activities on your
company's bottom line.
Integrate a contact form in your website easily. Forms submissions create leads
automatically in Odoo CRM. Leads can be used in marketing campaigns.
Manage your <a href="https://www.odoo.com/app/crm">sales funnel</a> with no
effort. Attract leads, follow-up on phone calls and meetings. Analyse the
quality of your leads to make informed decisions and save time by integrating
emails directly into the application.

View file

@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import controllers
from . import models
from . import report
from . import wizard

View file

@ -0,0 +1,141 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Email Marketing',
'summary': 'Design, send and track emails',
'version': '2.5',
'sequence': 60,
'website': 'https://www.odoo.com/app/email-marketing',
'category': 'Marketing/Email Marketing',
'depends': [
'contacts',
'mail',
'utm',
'link_tracker',
'web_editor',
'web_kanban_gauge',
'social_media',
'web_tour',
'digest',
],
'data': [
'security/res_groups_data.xml',
'security/ir.model.access.csv',
'data/digest_data.xml',
'data/ir_attachment_data.xml',
'data/ir_config_parameter_data.xml',
'data/ir_cron_data.xml',
'data/ir_module_data.xml',
'data/mailing_data_templates.xml',
'data/mailing_list_data.xml',
'data/res_users_data.xml',
'wizard/mail_compose_message_views.xml',
'wizard/mailing_contact_import_views.xml',
'wizard/mailing_contact_to_list_views.xml',
'wizard/mailing_list_merge_views.xml',
'wizard/mailing_mailing_test_views.xml',
'wizard/mailing_mailing_schedule_date_views.xml',
'report/mailing_trace_report_views.xml',
'views/mailing_filter_views.xml',
'views/mailing_trace_views.xml',
'views/link_tracker_views.xml',
'views/mailing_contact_views.xml',
'views/mailing_contact_subscription_views.xml',
'views/mailing_list_views.xml',
'views/mailing_mailing_views.xml',
'views/res_config_settings_views.xml',
'views/utm_campaign_views.xml',
'views/mailing_menus.xml',
'views/assets.xml',
'views/mailing_templates_portal_layouts.xml',
'views/mailing_templates_portal_management.xml',
'views/mailing_templates_portal_unsubscribe.xml',
'views/themes_templates.xml',
'views/snippets_themes.xml',
'views/snippets/s_alert.xml',
'views/snippets/s_blockquote.xml',
'views/snippets/s_call_to_action.xml',
'views/snippets/s_coupon_code.xml',
'views/snippets/s_cover.xml',
'views/snippets/s_color_blocks_2.xml',
'views/snippets/s_company_team.xml',
'views/snippets/s_comparisons.xml',
'views/snippets/s_event.xml',
'views/snippets/s_features.xml',
'views/snippets/s_features_grid.xml',
'views/snippets/s_hr.xml',
'views/snippets/s_image_text.xml',
'views/snippets/s_masonry_block.xml',
'views/snippets/s_media_list.xml',
'views/snippets/s_numbers.xml',
'views/snippets/s_picture.xml',
'views/snippets/s_product_list.xml',
'views/snippets/s_rating.xml',
'views/snippets/s_references.xml',
'views/snippets/s_showcase.xml',
'views/snippets/s_text_block.xml',
'views/snippets/s_text_highlight.xml',
'views/snippets/s_text_image.xml',
'views/snippets/s_three_columns.xml',
'views/snippets/s_title.xml',
],
'demo': [
'data/mass_mailing_demo.xml',
],
'application': True,
'assets': {
'mass_mailing.mailing_assets': [
'mass_mailing/static/src/scss/mailing_portal.scss',
'mass_mailing/static/src/js/mailing_portal.js',
],
'web.assets_backend': [
'mass_mailing/static/src/scss/mailing_filter_widget.scss',
'mass_mailing/static/src/scss/mass_mailing.scss',
'mass_mailing/static/src/scss/mass_mailing_mobile.scss',
'mass_mailing/static/src/scss/mass_mailing_mobile_preview.scss',
'mass_mailing/static/src/css/email_template.css',
'mass_mailing/static/src/views/*.js',
'mass_mailing/static/src/js/mailing_m2o_filter.js',
'mass_mailing/static/src/js/mass_mailing.js',
'mass_mailing/static/src/js/mass_mailing_design_constants.js',
'mass_mailing/static/src/js/mass_mailing_mobile_preview.js',
'mass_mailing/static/src/js/mass_mailing_html_field.js',
'mass_mailing/static/src/js/mailing_mailing_view_form_full_width.js',
'mass_mailing/static/src/xml/mailing_filter_widget.xml',
'mass_mailing/static/src/xml/mass_mailing.xml',
'mass_mailing/static/src/views/*.xml',
],
'mass_mailing.assets_mail_themes': [
'mass_mailing/static/src/scss/themes/**/*',
],
'mass_mailing.assets_mail_themes_edition': [
('include', 'web._assets_helpers'),
'web/static/src/scss/pre_variables.scss',
'web/static/lib/bootstrap/scss/_variables.scss',
'mass_mailing/static/src/scss/mass_mailing.ui.scss',
],
'web_editor.assets_wysiwyg': [
'mass_mailing/static/src/js/snippets.editor.js',
'mass_mailing/static/src/js/wysiwyg.js',
'mass_mailing/static/src/xml/mass_mailing.editor.xml',
'mass_mailing/static/src/scss/mass_mailing.wysiwyg.scss',
],
'web.assets_common': [
'mass_mailing/static/src/js/tours/**/*',
],
'web.assets_frontend': [
'mass_mailing/static/src/js/tours/**/*',
],
'web.qunit_suite_tests': [
'mass_mailing/static/tests/mass_mailing_favourite_filter_tests.js',
'mass_mailing/static/src/js/mass_mailing_snippets.js',
'mass_mailing/static/src/snippets/s_media_list/options.js',
'mass_mailing/static/src/snippets/s_showcase/options.js',
'mass_mailing/static/src/snippets/s_rating/options.js',
'mass_mailing/static/tests/mass_mailing_html_tests.js',
'mass_mailing/static/tests/mailing_mailing_view_form_tests.js',
],
},
'license': 'LGPL-3',
}

View file

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import main

View file

@ -0,0 +1,296 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
import urllib.parse
from odoo import _, exceptions, http, tools
from odoo.http import request, Response
from odoo.tools import consteq
from lxml import etree
from werkzeug.exceptions import BadRequest, NotFound
class MassMailController(http.Controller):
def _valid_unsubscribe_token(self, mailing_id, res_id, email, token):
if not (mailing_id and res_id and email and token):
return False
mailing = request.env['mailing.mailing'].sudo().browse(mailing_id)
return consteq(mailing._unsubscribe_token(res_id, email), token)
def _log_blacklist_action(self, blacklist_entry, mailing_id, description):
mailing = request.env['mailing.mailing'].sudo().browse(mailing_id)
model_display = mailing.mailing_model_id.display_name
blacklist_entry._message_log(body=description + " ({})".format(model_display))
# ------------------------------------------------------------
# SUBSCRIPTION MANAGEMENT
# ------------------------------------------------------------
# csrf is disabled here because it will be called by the MUA with unpredictable session at that time
@http.route(['/mail/mailing/<int:mailing_id>/unsubscribe_oneclick'], type='http', website=True, auth='public',
methods=["POST"], csrf=False)
def mailing_unsubscribe_oneclick(self, mailing_id, email=None, res_id=None, token="", **post):
self.mailing(mailing_id, email=email, res_id=res_id, token=token, **post)
return Response(status=200)
@http.route('/mailing/<int:mailing_id>/confirm_unsubscribe', type='http', website=True, auth='public')
def mailing_confirm_unsubscribe(self, mailing_id, email=None, res_id=None, token="", **post):
mailing = request.env['mailing.mailing'].sudo().browse(mailing_id)
# Check access (note that this will also raise AccessDenied if the mailing does not exist)
if not self._valid_unsubscribe_token(mailing_id, res_id, email, str(token)):
raise exceptions.AccessDenied()
unsubscribed_str = _("Are you sure you want to unsubscribe from our mailing list?")
# Display list name if list is public
if mailing.mailing_model_real == 'mailing.contact':
unsubscribed_lists = ', '.join(mailing_list.name for mailing_list in mailing.contact_list_ids if mailing_list.is_public)
if unsubscribed_lists:
unsubscribed_str = _(
'Are you sure you want to unsubscribe from the mailing list "%(unsubscribed_lists)s"?',
unsubscribed_lists=unsubscribed_lists
)
unsubscribe_btn = _("Unsubscribe")
template = etree.fromstring("""
<t t-call="mass_mailing.layout">
<div class="container o_unsubscribe_form">
<form action="/mailing/confirm_unsubscribe" method="POST" class="col-lg-6 offset-lg-3 mt-4">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<input type="hidden" name="email" t-att-value="email"/>
<input type="hidden" name="mailing_id" t-att-value="mailing_id"/>
<input type="hidden" name="res_id" t-att-value="res_id"/>
<input type="hidden" name="token" t-att-value="token"/>
<div id="info_state" class="alert alert-success">
<div class="text-center">
<p t-out="unsubscribed_str"/>
<button type="submit" class="btn btn-primary" t-out="unsubscribe_btn"/>
</div>
</div>
</form>
</div>
</t>
""")
return request.env['ir.qweb']._render(template, {
'main_object': mailing,
'token': token,
'email': email,
'mailing_id': mailing_id,
'unsubscribed_str': unsubscribed_str,
'res_id': res_id,
'unsubscribe_btn': unsubscribe_btn,
})
# kept for backwards compatibility, must eventually be merged with mailing/<mailing_id>/unsubscribe
@http.route('/mailing/confirm_unsubscribe', type='http', website=True, auth='public', methods=['POST'])
def mailing_confirm_unsubscribe_post(self, mailing_id, email=None, res_id=None, token="", **post):
url_params = urllib.parse.urlencode({'email': email, 'res_id': res_id, 'token': token})
url = f'/mail/mailing/{int(mailing_id)}/unsubscribe?{url_params}'
return request.redirect(url)
# todo: merge this route with /mail/mailing/confirm_unsubscribe on next minor version
@http.route(['/mail/mailing/<int:mailing_id>/unsubscribe'], type='http', website=True, auth='public')
def mailing(self, mailing_id, email=None, res_id=None, token="", **post):
mailing = request.env['mailing.mailing'].sudo().browse(mailing_id)
if mailing.exists():
res_id = res_id and int(res_id)
if not self._valid_unsubscribe_token(mailing_id, res_id, email, str(token)):
raise exceptions.AccessDenied()
if mailing.mailing_model_real == 'mailing.contact':
# Unsubscribe directly + Let the user choose their subscriptions
mailing.update_opt_out(email, mailing.contact_list_ids.ids, True)
contacts = request.env['mailing.contact'].sudo().search([('email_normalized', '=', tools.email_normalize(email))])
subscription_list_ids = contacts.mapped('subscription_list_ids')
# In many user are found : if user is opt_out on the list with contact_id 1 but not with contact_id 2,
# assume that the user is not opt_out on both
# TODO DBE Fixme : Optimise the following to get real opt_out and opt_in
opt_out_list_ids = subscription_list_ids.filtered(lambda rel: rel.opt_out).mapped('list_id')
opt_in_list_ids = subscription_list_ids.filtered(lambda rel: not rel.opt_out).mapped('list_id')
opt_out_list_ids = set([list.id for list in opt_out_list_ids if list not in opt_in_list_ids])
unique_list_ids = set([list.list_id.id for list in subscription_list_ids])
list_ids = request.env['mailing.list'].sudo().browse(unique_list_ids).filtered('active')
unsubscribed_list = ', '.join(str(list.name) for list in mailing.contact_list_ids if list.is_public)
return request.render('mass_mailing.page_unsubscribe', {
'contacts': contacts,
'list_ids': list_ids,
'opt_out_list_ids': opt_out_list_ids,
'unsubscribed_list': unsubscribed_list,
'email': email,
'mailing_id': mailing_id,
'res_id': res_id,
'show_blacklist_button': request.env['ir.config_parameter'].sudo().get_param('mass_mailing.show_blacklist_buttons'),
})
else:
opt_in_lists = request.env['mailing.contact.subscription'].sudo().search([
('contact_id.email_normalized', '=', email),
('opt_out', '=', False)
]).mapped('list_id').filtered('active')
blacklist_rec = request.env['mail.blacklist'].sudo()._add(email)
self._log_blacklist_action(
blacklist_rec, mailing_id,
_("""Requested blacklisting via unsubscribe link."""))
return request.render('mass_mailing.page_unsubscribed', {
'email': email,
'mailing_id': mailing_id,
'res_id': res_id,
'list_ids': opt_in_lists,
'show_blacklist_button': request.env['ir.config_parameter'].sudo().get_param(
'mass_mailing.show_blacklist_buttons'),
})
return request.redirect('/web')
@http.route('/mail/mailing/unsubscribe', type='json', auth='public')
def unsubscribe(self, mailing_id, opt_in_ids, opt_out_ids, email, res_id, token):
mailing = request.env['mailing.mailing'].sudo().browse(mailing_id)
if mailing.exists():
if not self._valid_unsubscribe_token(mailing_id, res_id, email, token):
return 'unauthorized'
mailing.update_opt_out(email, opt_in_ids, False)
mailing.update_opt_out(email, opt_out_ids, True)
return True
return 'error'
@http.route('/mailing/feedback', type='json', auth='public')
def send_feedback(self, mailing_id, res_id, email, feedback, token):
mailing = request.env['mailing.mailing'].sudo().browse(mailing_id)
if mailing.exists() and email:
if not self._valid_unsubscribe_token(mailing_id, res_id, email, token):
return 'unauthorized'
model = request.env[mailing.mailing_model_real]
records = model.sudo().search([('email_normalized', '=', tools.email_normalize(email))])
for record in records:
record.sudo().message_post(body=_("Feedback from %(email)s: %(feedback)s", email=email, feedback=feedback))
return bool(records)
return 'error'
@http.route(['/unsubscribe_from_list'], type='http', website=True, multilang=False, auth='public', sitemap=False)
def unsubscribe_placeholder_link(self, **post):
"""Dummy route so placeholder is not prefixed by language, MUST have multilang=False"""
raise NotFound()
# ------------------------------------------------------------
# TRACKING
# ------------------------------------------------------------
@http.route('/mail/track/<int:mail_id>/<string:token>/blank.gif', type='http', auth='public')
def track_mail_open(self, mail_id, token, **post):
""" Email tracking. """
if not consteq(token, tools.hmac(request.env(su=True), 'mass_mailing-mail_mail-open', mail_id)):
raise BadRequest()
request.env['mailing.trace'].sudo().set_opened(domain=[('mail_mail_id_int', 'in', [mail_id])])
response = Response()
response.mimetype = 'image/gif'
response.data = base64.b64decode(b'R0lGODlhAQABAIAAANvf7wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==')
return response
@http.route('/r/<string:code>/m/<int:mailing_trace_id>', type='http', auth="public")
def full_url_redirect(self, code, mailing_trace_id, **post):
# don't assume geoip is set, it is part of the website module
# which mass_mailing doesn't depend on
country_code = request.geoip.get('country_code')
request.env['link.tracker.click'].sudo().add_click(
code,
ip=request.httprequest.remote_addr,
country_code=country_code,
mailing_trace_id=mailing_trace_id
)
redirect_url = request.env['link.tracker'].get_url_from_code(code)
if not redirect_url:
raise NotFound()
return request.redirect(redirect_url, code=301, local=False)
# ------------------------------------------------------------
# MAILING MANAGEMENT
# ------------------------------------------------------------
@http.route('/mailing/report/unsubscribe', type='http', website=True, auth='public')
def turn_off_mailing_reports(self, token, user_id):
if not token or not user_id:
raise NotFound()
user_id = int(user_id)
correct_token = consteq(token, request.env['mailing.mailing']._get_unsubscribe_token(user_id))
user = request.env['res.users'].sudo().browse(user_id)
if correct_token and user.has_group('mass_mailing.group_mass_mailing_user'):
request.env['ir.config_parameter'].sudo().set_param('mass_mailing.mass_mailing_reports', False)
if user.has_group('base.group_system'):
menu_id = request.env.ref('mass_mailing.menu_mass_mailing_global_settings').id
return request.render('mass_mailing.mailing_report_deactivated', {'menu_id': menu_id})
return request.render('mass_mailing.mailing_report_deactivated')
raise NotFound()
@http.route(['/mailing/<int:mailing_id>/view'], type='http', website=True, auth='public')
def view(self, mailing_id, email=None, res_id=None, token=""):
mailing = request.env['mailing.mailing'].sudo().browse(mailing_id)
if mailing.exists():
res_id = int(res_id) if res_id else False
if not self._valid_unsubscribe_token(mailing_id, res_id, email, str(token)) and not request.env.user.has_group('mass_mailing.group_mass_mailing_user'):
raise exceptions.AccessDenied()
html_markupsafe = mailing._render_field('body_html', [res_id])[res_id]
# Update generic URLs (without parameters) to final ones
html_markupsafe = html_markupsafe.replace('/unsubscribe_from_list',
mailing._get_unsubscribe_url(email, res_id))
return request.render('mass_mailing.view', {
'body': html_markupsafe,
})
return request.redirect('/web')
# ------------------------------------------------------------
# BLACKLIST
# ------------------------------------------------------------
@http.route('/mailing/blacklist/check', type='json', auth='public')
def blacklist_check(self, mailing_id, res_id, email, token):
if not self._valid_unsubscribe_token(mailing_id, res_id, email, token):
return 'unauthorized'
if email:
record = request.env['mail.blacklist'].sudo().with_context(active_test=False).search([('email', '=', tools.email_normalize(email))])
if record['active']:
return True
return False
return 'error'
@http.route('/mailing/blacklist/add', type='json', auth='public')
def blacklist_add(self, mailing_id, res_id, email, token):
if not self._valid_unsubscribe_token(mailing_id, res_id, email, token):
return 'unauthorized'
if email:
blacklist_rec = request.env['mail.blacklist'].sudo()._add(email)
self._log_blacklist_action(
blacklist_rec, mailing_id,
_("""Requested blacklisting via unsubscription page."""))
return True
return 'error'
@http.route('/mailing/blacklist/remove', type='json', auth='public')
def blacklist_remove(self, mailing_id, res_id, email, token):
if not self._valid_unsubscribe_token(mailing_id, res_id, email, token):
return 'unauthorized'
if email:
blacklist_rec = request.env['mail.blacklist'].sudo()._remove(email)
self._log_blacklist_action(
blacklist_rec, mailing_id,
_("""Requested de-blacklisting via unsubscription page."""))
return True
return 'error'
# ------------------------------------------------------------
# MISCELLANEOUS
# ------------------------------------------------------------
@http.route('/mailing/get_preview_assets', type='json', auth='user')
def get_mobile_preview_styling(self):
""" This route allows a rpc call to get the styling needed for email template conversion.
We do this to avoid duplicating the template."""
if not request.env.user.has_group('mass_mailing.group_mass_mailing_user'):
raise NotFound
return request.env['ir.qweb']._render('mass_mailing.iframe_css_assets_edit')

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<template id="digest_mail_main" inherit_id="digest.digest_mail_main">
<xpath expr="//div[hasclass('by_odoo')]/t[last()]" position="after">
<t t-if="mailing_report_token">
<a t-attf-href="/mailing/report/unsubscribe?token=#{mailing_report_token}&amp;user_id=#{user_id}"
target="_blank" style="text-decoration: none;">
<span style="color: #8f8f8f;">Turn off Mailing Reports</span>
</a>
</t>
</xpath>
</template>
</data>
</odoo>

View file

@ -0,0 +1,162 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
<!-- Snippets' Default Images -->
<record id="mass_mailing.s_cover_default_image" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_cover_default_image.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_cover.jpg</field>
</record>
<record id="mass_mailing.s_media_list_default_image_1" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_media_list_default_image_1.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_media_list_1.jpg</field>
</record>
<record id="mass_mailing.s_media_list_default_image_2" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_media_list_default_image_2.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_media_list_2.jpg</field>
</record>
<record id="mass_mailing.s_media_list_default_image_3" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_media_list_default_image_3.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_media_list_3.jpg</field>
</record>
<record id="mass_mailing.s_company_team_default_image_1" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_company_team_default_image_1.png</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_team_member_1.png</field>
</record>
<record id="mass_mailing.s_company_team_default_image_2" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_company_team_default_image_2.png</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_team_member_2.png</field>
</record>
<record id="mass_mailing.s_company_team_default_image_3" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_company_team_default_image_3.png</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_team_member_3.png</field>
</record>
<record id="mass_mailing.s_company_team_default_image_4" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_company_team_default_image_4.png</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_team_member_4.png</field>
</record>
<record id="mass_mailing.s_reference_default_image_1" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_reference_default_image_1.png</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_references_1.png</field>
</record>
<record id="mass_mailing.s_reference_default_image_2" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_reference_default_image_2.png</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_references_2.png</field>
</record>
<record id="mass_mailing.s_reference_default_image_3" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_reference_default_image_3.png</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_references_3.png</field>
</record>
<record id="mass_mailing.s_reference_default_image_4" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_reference_default_image_4.png</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_references_4.png</field>
</record>
<record id="mass_mailing.s_product_list_default_image_1" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_product_list_default_image_1.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_product_1.jpg</field>
</record>
<record id="mass_mailing.s_product_list_default_image_2" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_product_list_default_image_2.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_product_2.jpg</field>
</record>
<record id="mass_mailing.s_product_list_default_image_3" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_product_list_default_image_3.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_product_3.jpg</field>
</record>
<record id="mass_mailing.s_blockquote_default_image" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_blockquote_default_image.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_team_member_2.png</field>
</record>
<record id="mass_mailing.s_image_text_default_image" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_image_text_default_image.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_image_text.jpg</field>
</record>
<record id="mass_mailing.s_event_default_image_1" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_event_default_image_1.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_event_1.jpg</field>
</record>
<record id="mass_mailing.s_event_default_image_2" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_event_default_image_2.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_event_2.jpg</field>
</record>
<record id="mass_mailing.s_masonry_block_default_image_1" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_masonry_block_default_image_1.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_masonry_block_1.jpg</field>
</record>
<record id="mass_mailing.s_masonry_block_default_image_2" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_masonry_block_default_image_2.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_masonry_block_2.jpg</field>
</record>
<record id="mass_mailing.s_picture_default_image" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_picture_default_image.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_picture.jpg</field>
</record>
<record id="mass_mailing.s_text_image_default_image" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_text_image_default_image.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_text_image.jpg</field>
</record>
<record id="mass_mailing.s_three_columns_default_image_1" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_three_columns_default_image_1.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_three_cols_1.jpg</field>
</record>
<record id="mass_mailing.s_three_columns_default_image_2" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_three_columns_default_image_2.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_three_cols_2.jpg</field>
</record>
<record id="mass_mailing.s_three_columns_default_image_3" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_three_columns_default_image_3.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_three_cols_3.jpg</field>
</record>
</data>
</odoo>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Enable Mass Mailing Reports -->
<function model="ir.config_parameter"
name="set_param"
eval="('mass_mailing.mass_mailing_reports', 'True')"/>
</data>
</odoo>

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Cron that processes the mass mailing queue -->
<record id="ir_cron_mass_mailing_queue" model="ir.cron">
<field name="name">Mail Marketing: Process queue</field>
<field name="model_id" ref="model_mailing_mailing"/>
<field name="state">code</field>
<field name="code">model._process_mass_mailing_queue()</field>
<field name="user_id" ref="base.user_root" />
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field eval="False" name="doall" />
</record>
<!-- Cron that processes the a/b testing -->
<record id="ir_cron_mass_mailing_ab_testing" model="ir.cron">
<field name="name">Mail Marketing: A/B Testing</field>
<field name="model_id" ref="model_utm_campaign"/>
<field name="state">code</field>
<field name="code">model._cron_process_mass_mailing_ab_testing()</field>
<field name="user_id" ref="base.user_root"/>
<field name="active" eval="False"/>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="doall" eval="True"/>
</record>
</data>
</odoo>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
<record model="ir.module.category" id="base.module_category_marketing_email_marketing">
<field name="sequence">19</field>
<field name="description">Helps you manage your mass mailing to design
professional emails and reuse templates.</field>
</record>
</data>
</odoo>

View file

@ -0,0 +1,183 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!--
Reference: https://litmus.com/community/learning/24-how-to-code-a-responsive-email-from-scratch
https://www.campaignmonitor.com/css/link-element/link-in-head/
-->
<template id="mass_mailing_mail_layout">
&lt;!DOCTYPE html&gt;
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="format-detection" content="telephone=no"/>
<meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=no;"/>
<meta http-equiv="X-UA-Compatible" content="IE=9; IE=8; IE=7; IE=EDGE" />
<t t-call="mass_mailing.mass_mailing_mail_style"/>
<!--
Prevent Outlook from distorting images with DPI scaling (see
https://www.htmlemailcheck.com/knowledge-base/dpi-scaling-in-outlook-2007-2013/):
-->
<!--[if mso]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
</head>
<body>
<t t-out="body"/>
</body>
</html>
</template>
<template id="mass_mailing_mail_style">
<style>
.o_layout * {
box-sizing: border-box !important;
}
.o_layout :not(.fa) {
font-family: Arial,Helvetica Neue,Helvetica,sans-serif;
}
/* Remove space around the email design. */
html,
body {
margin: 0 auto !important;
padding: 0 !important;
height: 100% !important;
width: 100% !important;
}
/* Stop Outlook resizing small text. */
* {
-ms-text-size-adjust: 100%;
}
/* Stop Outlook from adding extra spacing to tables. */
table,
td {
mso-table-lspace: 0pt !important;
mso-table-rspace: 0pt !important;
}
/* Use a better rendering method when resizing images in Outlook IE. */
img {
-ms-interpolation-mode:bicubic;
}
/* Prevent Windows 10 Mail from underlining links. Styles for underlined links should be inline. */
a {
text-decoration: none;
}
@media screen and (max-width: 768px) {
.o_mail_snippet_general .container {
width: 100% !important;
}
.s_header_social table td, .s_header_text_social table td, .s_header_logo table td,
.o_mail_block_footer_social td,
.s_showcase td,
.s_mail_product_list td,
.s_references td {
text-align: center !important;
}
}
@media screen and (max-width: 1135px) {
.o_stacking_wrapper {
width: 100% !important;
height: unset !important;
}
td {
max-width: inherit !important;
}
img:only-child:not(.img-fluid) {
object-fit: cover;
min-width: 100% !important;
}
.o_desktop_h100 {
height: unset !important;
}
}
</style>
</template>
</data>
<data noupdate="0">
<template id="ab_testing_description" name="Mass Mailing: A/B Test Description">
<div name="ab_testing_description" class="mb-2">
<t t-if="mailing.ab_testing_completed">
<p t-if="mailing.ab_testing_pc == 100">
This <t t-out="mailing.mailing_type_description"/> is the winner of the A/B testing campaign and has been sent to all remaining recipients.
</p>
<p t-else="">The winner has already been sent. Use <b>Compare Version</b> to get an overview of this A/B testing campaign.</p>
</t>
<t t-elif="mailing.ab_testing_mailings_count >= 2">
<p>
A sample of <b><t t-out="mailing.ab_testing_pc"/>% of recipients</b> will receive this <b><t t-out="mailing.mailing_type_description"/></b>, and <t t-out="other_ab_testing_pc"/>% receive one of the
<b><t t-out="mailing.ab_testing_mailings_count - 1"/> other versions</b> from the same campaign.
</p>
<p>
<t t-if="mailing.ab_testing_winner_selection == 'manual'">Don't forget to send your preferred version</t>
<t t-elif="not mailing.ab_testing_schedule_datetime">Since the date and time for this test has not been scheduled, don't forget to manually send your preferred version.</t>
<t t-else="">
Then on <b><t t-out="mailing.ab_testing_schedule_datetime.strftime('%b %d, %Y')"/></b> the <t t-out="mailing.mailing_type_description"/> having the <b><t t-out="ab_testing_winner_selection_description"/></b> will be sent
</t> to the remaining <t t-out="remaining_ab_testing_pc"/>% of recipients.
</p>
</t>
<t t-else="">
<p>
A sample of <b><t t-out="mailing.ab_testing_pc"/>% of recipients</b> will receive this <b><t t-out="mailing.mailing_type_description"/></b>.
</p>
<p>Try different variations in the campaign to compare their <t t-out="ab_testing_winner_selection_description"/>.</p>
<p>
<t t-if="mailing.ab_testing_winner_selection != 'manual'">Once the best version is identified, we will send the best one to the remaining recipients.</t>
<t t-else="">
The actual <t t-out="mailing.mailing_type_description"/> will be sent to the remaining recipients.
</t>
</p>
</t>
</div>
</template>
<template id="mass_mailing.mass_mailing_kpi_link_trackers" name="Marketing: mailing link trackers statistic">
<div class="global_layout" t-if="link_trackers">
<table bgcolor="#ffffff" cellspacing="0" cellpadding="0" width="650" align="center" border="0" style="width: 100%; max-width: 650px;">
<tr>
<td style="width: 100%;">
<table cellspacing="0" cellpadding="0" border="0" width="580" align="center" style="width:100%; max-width:580px;">
<tr>
<td align="left">
<span style="color:#282f33; font-size: 15px; font-weight: bold; line-height: 30px">
<t t-esc="'Click Rate Report on %i %s Sent' % (object.expected, mailing_type)"/>
</span>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td style="margin: 0; padding:0;">
<table cellspacing="0" cellpadding="0" border="0" width="580" align="center" style="width:100%; max-width:580px;border-collapse: collapse;">
<tr style="color: #875a7b; font-size: 16px; font-weight: 500;">
<td style="width: 70%;padding: 10px 0; text-align: center; border: 1px solid #e7e7e7;">Button Label</td>
<td style="width: 30%;padding: 10px 0; text-align: center; border: 1px solid #e7e7e7;">%Click (Total)</td>
</tr>
<tr t-foreach="link_trackers" t-as="link_tracker" style="color: #888888; font-size: 15px; font-weight: 300;">
<td style="width: 70%;padding: 10px 0; border: 1px solid #e7e7e7;">
<a t-att-href="link_tracker.absolute_url" target="_blank" style="color: #56b3b5; text-decoration: none;" t-esc="link_tracker.label or link_tracker.url"/>
</td>
<td style="width: 30%;padding: 10px 0; text-align: center; border: 1px solid #e7e7e7;">
<t t-esc="int(link_tracker.count * 100 / object.sent) if object.sent else 0"/>% (<t t-esc="link_tracker.count"/>)
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
</template>
</data>
</odoo>

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="mailing_list_data" model="mailing.list">
<field name="name">Newsletter</field>
</record>
<record id="mass_mailing_contact_0" model="mailing.contact">
<field name="name" model="res.users" eval="obj().env.ref('base.user_admin').name"/>
<field name="email" model="res.users" eval="obj().env.ref('base.user_admin').email"/>
<field name="list_ids" eval="[(6,0,[ref('mass_mailing.mailing_list_data')])]"/>
</record>
</data>
</odoo>

View file

@ -0,0 +1,296 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="mass_mail_attach_1" model="ir.attachment">
<field name="datas">bWlncmF0aW9uIHRlc3Q=</field>
<field name="name">SampleDoc.doc</field>
</record>
<!-- Create Extra Mailing List for Demo -->
<record id="mailing_list_1" model="mailing.list">
<field name="name">Imported Contacts</field>
</record>
<!-- Create Contacts -->
<record id="mass_mail_contact_1" model="mailing.contact">
<field name="name">Aristide Antario</field>
<field name="email">alexandre.antario@example.com</field>
<field name="list_ids" eval="[(6,0,[ref('mass_mailing.mailing_list_data')])]"/>
</record>
<record id="mass_mail_contact_2" model="mailing.contact">
<field name="name">Beverly Bridge</field>
<field name="email">beverly.bridge@example.com</field>
<field name="list_ids" eval="[(6,0,[ref('mass_mailing.mailing_list_data')])]"/>
</record>
<record id="mass_mail_contact_3" model="mailing.contact">
<field name="name">Carol Cartridge</field>
<field name="email">carol.cartridge@example.com</field>
<field name="list_ids" eval="[(6,0,[ref('mass_mailing.mailing_list_data'),ref('mass_mailing.mailing_list_1')])]"/>
</record>
<record id="mass_mail_contact_4" model="mailing.contact">
<field name="name">David Dawson</field>
<field name="email">david.dawson@example.com</field>
</record>
<record id="mass_mail_contact_5" model="mailing.contact">
<field name="name">Elsa Ericson</field>
<field name="email">elsa.ericson@example.com</field>
<field name="message_bounce">5</field>
<field name="list_ids" eval="[(6,0,[ref('mass_mailing.mailing_list_data')])]"/>
</record>
<record id="mass_mail_contact_6" model="mailing.contact">
<field name="name">Franz Faubourg</field>
<field name="email">franz.faubourg@example.com</field>
<field name="list_ids" eval="[(6,0,[ref('mass_mailing.mailing_list_1')])]"/>
</record>
<!-- Create Opt-out Records -->
<record id="mass_mail_contact_list_rel_1" model="mailing.contact.subscription">
<field name="list_id" ref="mass_mailing.mailing_list_data"/>
<field name="contact_id" ref="mass_mailing.mass_mail_contact_4"/>
<field name="opt_out">True</field>
</record>
<record id="mass_mail_contact_list_rel_2" model="mailing.contact.subscription">
<field name="list_id" ref="mass_mailing.mailing_list_data"/>
<field name="contact_id" ref="mass_mailing.mass_mail_contact_6"/>
<field name="opt_out">True</field>
</record>
<!-- Create Blacklist Records -->
<record id="blacklist_1" model="mail.blacklist">
<field name="email">elsa.ericson@example.com</field>
</record>
<!-- Create campaign and mailings -->
<record id="utm_source_0" model="utm.source">
<field name="name">Newsletter 1</field>
</record>
<record id="mass_mail_campaign_1" model="utm.campaign">
<field name="name">Newsletter</field>
<field name="stage_id" ref="utm.campaign_stage_1"/>
<field name="user_id" ref="base.user_admin"/>
<field name="tag_ids" eval="[(6,0,[ref('utm.utm_tag_1')])]"/>
</record>
<record id="mass_mail_1" model="mailing.mailing">
<field name="name">Newsletter 1</field>
<field name="subject">Monthly Newsletter</field>
<field name="state">done</field>
<field name="user_id" ref="base.user_admin"/>
<field name="email_from">info@yourcompany.example.com</field>
<field name="sent_date" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="campaign_id" ref="mass_mail_campaign_1"/>
<field name="source_id" ref="mass_mailing.utm_source_0"/>
<field name="mailing_model_id" ref="base.model_res_partner"/>
<field name="mailing_domain" eval="[('parent_id', '=', ref('base.res_partner_4'))]"/>
<field name="reply_to_mode">new</field>
<field name="reply_to">Info &lt;info@yourcompany.example.com&gt;</field>
<field name="body_arch" type="html">
<div class="o_layout o_default_theme oe_unremovable oe_unmovable" data-name="Mailing">
<div class="container o_mail_wrapper oe_unremovable oe_unmovable" style="border-collapse:collapse;">
<div class="row">
<div class="col o_mail_no_options o_mail_wrapper_td bg-white oe_structure o_editable" style="text-align:left;width:100%;">
<div class="o_mail_block_header_logo" data-snippet="s_mail_block_header_logo">
<div class="o_mail_snippet_general" style="margin:0px auto 0px auto;background-color:rgb(255, 255, 255);max-width:600px;width:100%;">
<div class="container o_mail_h_padding" style="padding:0 20px 0 20px;width:100%;border-collapse:separate;">
<div class="row">
<div valign="center" width="30%" class="col text-center o_mail_v_padding pb0" style="padding:20px 0 0px 0;vertical-align:middle;text-align:center;">
<a href="http://www.example.com" style="text-decoration:none;font-weight:bold;background-color:transparent;color:rgb(100, 89, 116);">
<img border="0" src="/mass_mailing/static/src/img/theme_default/s_default_image_header_logo.png" style="border-style:none;height:auto;vertical-align:middle;max-width:400px;width:auto"/>
</a>
</div>
</div>
</div>
</div>
</div>
<div class="o_mail_block_footer_separator" data-snippet="s_hr" style="margin:0 20px 0 20px;">
<div class="o_mail_snippet_general" style="margin:0px auto 0px auto;background-color:rgb(255, 255, 255);max-width:600px;width:100%;">
<div class="container" style="width:100%;border-collapse:separate;">
<div class="row">
<div valign="top" style="padding:20px 0 20px 0;text-align:left;vertical-align:top;width:100%;" class="col o_mail_v_padding o_mail_no_colorpicker">
<div style="background-color:rgb(245, 245, 245);height:2px;width:100%;" class="separator"></div>
</div>
</div>
</div>
</div>
</div>
<div class="o_mail_block_text" data-snippet="s_text_block">
<div class="o_mail_snippet_general" style="margin:0px auto 0px auto;background-color:rgb(255, 255, 255);max-width:600px;width:100%;">
<div class="container" style="width:100%;border-collapse:separate;">
<div class="row">
<div class="col-12 o_mail_h_padding o_mail_v_padding o_mail_no_colorpicker" style="padding:20px;text-align:left;vertical-align:top;">
<p style="margin:0px 0 1rem 0;font-size:14px;">
Great stories have personality. Consider telling a great story that provides personality. Writing a story with personality for potential clients will assist with making a relationship connection. This shows up in small quirks like word choices or phrases. Write from your point of view, not from someone else's experience.
<br/>Great stories are for everyone even when only written for just one person. If you try to write with a wide general audience in mind, your story will ring false and be bland. No one will be interested. Write for one person. If its genuine for the one, its genuine for the rest.
</p>
</div>
</div>
</div>
</div>
</div>
<div class="o_mail_block_footer_social o_mail_footer_social_center" data-snippet="s_mail_block_footer_social">
<div class="o_mail_snippet_general" style="margin:0px auto 0px auto;background-color:rgb(255, 255, 255);max-width:600px;width:100%;">
<div align="center" class="container" style="border-style:solid none none none;padding:20px 0 20px 0;border-top-color:rgb(245, 245, 245);border-top-width:2px;width:100%;border-collapse:separate;">
<div class="row">
<div class="col o_mail_footer_links o_default_snippet_text" style="padding:10px 0 10px 0;text-align:center;vertical-align:middle;">
<a href="/unsubscribe_from_list" class="btn btn-link o_default_snippet_text" style="text-decoration:none;border-radius:0.25rem;border-style:solid;padding:0px;cursor:pointer;line-height:1.5;font-size:12px;border-start-color:transparent;border-bottom-color:transparent;border-end-color:transparent;border-top-color:transparent;border-start-width:1px;border-bottom-width:1px;border-end-width:1px;border-top-width:1px;user-select:none;vertical-align:middle;white-space:nowrap;text-align:center;font-weight:bold;display:inline-block;background-color:transparent;color:rgb(100, 89, 116);">Unsubscribe</a> |
<a href="/contactus" class="btn btn-link o_default_snippet_text" style="text-decoration:none;border-radius:0.25rem;border-style:solid;padding:0px;cursor:pointer;line-height:1.5;font-size:12px;border-start-color:transparent;border-bottom-color:transparent;border-end-color:transparent;border-top-color:transparent;border-start-width:1px;border-bottom-width:1px;border-end-width:1px;border-top-width:1px;user-select:none;vertical-align:middle;white-space:nowrap;text-align:center;font-weight:bold;display:inline-block;background-color:transparent;color:rgb(100, 89, 116);">Contact</a>
</div>
</div>
<div class="row">
<div class="col" style="text-align:left;vertical-align:middle;">
<p class="o_mail_footer_copy" style="margin:0px 0 1rem 0;text-align:center;font-weight:bold;color:rgb(147, 146, 146);font-size:9px;">
<img src="/web_editor/font_to_img/61945/rgb(147,146,146)/9" data-class="fa fa-copyright" style="border-style:none;max-width:100%;width:100%;vertical-align:middle;height: auto; width: auto;"/>2018 All Rights Reserved
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div align="center" class="container" style="width:100%;border-collapse:separate;">
<div class="row">
<div align="center" style="padding:16px 0 16px 0;" class="col pt16 pb16">
Powered by <a target="_blank" href="https://www.odoo.com" style="text-decoration:none;background-color:transparent;color:#875A7B;">Odoo</a>
</div>
</div>
</div>
</div>
</field>
<field name="attachment_ids" eval="[(4, ref('mass_mail_attach_1'))]"/>
</record>
<!-- Generate link tracker information from it -->
<function model="mailing.mailing" name="convert_links" eval="[ref('mass_mailing.mass_mail_1')]"/>
<record id="mass_mail_1_stat_0" model="mailing.trace">
<field name="mass_mailing_id" ref="mass_mail_1"/>
<field name="message_id">1111000@odoo.com</field>
<field name="model">res.partner</field>
<field name="res_id" ref="base.res_partner_address_7"/>
<field name="email">billy.fox45@example.com</field>
<field name="trace_status">reply</field>
<field name="sent_datetime" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="open_datetime" eval="(DateTime.today() - relativedelta(days=4)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="reply_datetime" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="write_date" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/>
</record>
<record id="mass_mail_1_stat_1" model="mailing.trace">
<field name="mass_mailing_id" ref="mass_mail_1"/>
<field name="message_id">1111001@odoo.com</field>
<field name="model">res.partner</field>
<field name="res_id" ref="base.res_partner_address_13"/>
<field name="email">kim.snyder96@example.com</field>
<field name="trace_status">reply</field>
<field name="sent_datetime" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="open_datetime" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="reply_datetime" eval="(DateTime.today() - relativedelta(days=0)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="write_date" eval="(DateTime.today() - relativedelta(days=0)).strftime('%Y-%m-%d %H:%M:%S')"/>
</record>
<record id="mass_mail_1_stat_2" model="mailing.trace">
<field name="mass_mailing_id" ref="mass_mail_1"/>
<field name="message_id">1111002@odoo.com</field>
<field name="model">res.partner</field>
<field name="res_id" ref="base.res_partner_address_14"/>
<field name="email">edith.sanchez68@example.com</field>
<field name="trace_status">open</field>
<field name="sent_datetime" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="open_datetime" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="write_date" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
</record>
<record id="mass_mail_1_stat_3" model="mailing.trace">
<field name="mass_mailing_id" ref="mass_mail_1"/>
<field name="message_id">1111003@odoo.com</field>
<field name="model">res.partner</field>
<field name="res_id" ref="base.res_partner_address_24"/>
<field name="email">theodore.gardner36@example.com</field>
<field name="trace_status">open</field>
<field name="sent_datetime" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="open_datetime" eval="(DateTime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="write_date" eval="(DateTime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
</record>
<record id="mass_mail_1_stat_4" model="mailing.trace">
<field name="mass_mailing_id" ref="mass_mail_1"/>
<field name="message_id">1111004@odoo.com</field>
<field name="model">res.partner</field>
<field name="res_id" ref="base.res_partner_address_32"/>
<field name="email">sandra.neal80@example.com</field>
<field name="trace_status">sent</field>
<field name="sent_datetime" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="write_date" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
</record>
<record id="mass_mail_1_stat_5" model="mailing.trace">
<field name="mass_mailing_id" ref="mass_mail_1"/>
<field name="message_id">1111005@odoo.com</field>
<field name="model">res.partner</field>
<field name="res_id" ref="base.res_partner_address_33"/>
<field name="email">julie.richards84@example.com</field>
<field name="trace_status">error</field>
<field name="sent_datetime" eval="False"/>
</record>
<record id="mass_mail_1_stat_6" model="mailing.trace">
<field name="mass_mailing_id" ref="mass_mail_1"/>
<field name="message_id">1111006@odoo.com</field>
<field name="model">res.partner</field>
<field name="res_id" ref="base.res_partner_address_34"/>
<field name="email">travis.mendoza24@example.com</field>
<field name="trace_status">bounce</field>
<field name="sent_datetime" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="write_date" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/>
</record>
<record id="mass_mail_1_stat_7" model="mailing.trace">
<field name="mass_mailing_id" ref="mass_mail_1"/>
<field name="message_id">1111007@odoo.com</field>
<field name="model">res.partner</field>
<field name="res_id" ref="base.res_partner_address_34"/>
<field name="email">travis.mendoza24@example.com</field>
<field name="trace_status">bounce</field>
<field name="sent_datetime" eval="False"/>
</record>
<!-- Generate some clicks -->
<function model="link.tracker.click" name="add_click">
<value model="link.tracker.code"
search="[('link_id.url', '=', 'http://www.example.com')]"
use="code"/>
<value name="ip">100.01.02.03</value>
<value name="country_code">BE</value>
<value name="mailing_trace_id" eval="ref('mass_mail_1_stat_0')"/>
</function>
<function model="link.tracker.click" name="add_click">
<value model="link.tracker.code"
search="[('link_id.url', '=', 'http://www.example.net/page/contactus')]"
use="code"/>
<value name="ip">100.01.02.03</value>
<value name="country_code">BE</value>
<value name="mailing_trace_id" eval="ref('mass_mail_1_stat_0')"/>
</function>
<function model="link.tracker.click" name="add_click">
<value model="link.tracker.code"
search="[('link_id.url', '=', 'http://www.example.com')]"
use="code"/>
<value name="ip">100.01.02.04</value>
<value name="country_code">BE</value>
<value name="mailing_trace_id" eval="ref('mass_mail_1_stat_1')"/>
</function>
<function model="link.tracker.click" name="add_click">
<value model="link.tracker.code"
search="[('link_id.url', '=', 'http://www.example.net/page/contactus')]"
use="code"/>
<value name="ip">100.01.02.04</value>
<value name="country_code">BE</value>
<value name="mailing_trace_id" eval="ref('mass_mail_1_stat_0')"/>
</function>
<function model="link.tracker.click" name="add_click">
<value model="link.tracker.code"
search="[('link_id.url', '=', 'http://www.example.com')]"
use="code"/>
<value name="ip">100.01.02.05</value>
<value name="country_code">BE</value>
<value name="mailing_trace_id" eval="ref('mass_mail_1_stat_2')"/>
</function>
</data>
</odoo>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="base.default_user" model="res.users">
<field name="groups_id" eval="[(4,ref('mass_mailing.group_mass_mailing_user'))]"/>
</record>
</data>
</odoo>

View file

@ -0,0 +1,9 @@
.. _changelog:
Changelog
=========
`trunk (saas-2)`
----------------
- added module

View file

@ -0,0 +1,13 @@
Mass Mailing module documentation
=================================
Mass Mailing documentation topics
'''''''''''''''''''''''''''''''''
Changelog
'''''''''
.. toctree::
:maxdepth: 1
changelog.rst

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more