[REF] mass_mailing(_sms): improve mail/sms and traces failed/ignored state management
RATIONALE
Currently there are differences between mail and sms error management
especially when sending them in batch (mass mode). Moreover cancel
(ignored) and error (exception) states meaning is not clear. Finally
some failure types management between mail and sms can be cleaned.
SPECIFICATIONS
Meaning of ignored / error we want to enforce now is
* error: there was something wrong at sending and user has an action to
perform, i.e. server failed -> check its logs;
* canceled: invalid recipients due to contact information or mailing
configuration (blacklist, opt out, void or invalid email or phone number).
In indicates issues linked to records themselves;
In this task we also add failure information granularity on mailing traces
linked to email like what is done currently on SMS. This can be related to
mailing (blacklist, optout, duplicates) or related to recipient (no recipient,
incorrectly formatted).
We also correctly distinguish optout from blacklist when sending SMS.
SPECIFIC USE CASES
* recipient without email / number: mail / sms is set as canceled, trace is
ignored;
* recipient with invalid email (no @) / number (formatting impossible):
mail / sms is set as canceled, trace is ignored;
-> we now distinguish when possible a void email from a wrong email using
a newly-added selection key (mail_email_missing);
* recipient with email / number blacklisted: mail / sms is canceled, trace
is ignored;
* recipient with email / number that optouted from mailing: mail / sms is
canceled, trace is ignored;
* recipient with email that bounces: mail is sent and will be set as bounce
when receiving bounce in gateway; trace follow same path;
* recipient with number that bounces: not supported as currently no support
of bounce through IAP;
* mail server error, IAP error: mail / sms is set as exception / error,
trace is set in exception;
This means we introduce new failure types on mailing.trace model to reflect
those failure types
* ``mail_missing``: missing email (different from wrong value);
* ``mail_bl``: blacklisted;
* ``mail_optout``: optouted;
* ``mail_dup``: duplicated email skipped during mass email send;
We introduce ``sms_optout`` on SMS and trace models as it was merged with
blacklist previously. Now both errors are distinguished.
LINKS
Task ID-2377974
Community PR odoo/odoo#61467
Enterprise PR odoo/enterprise#14633
Upgrade PR odoo/upgrade#1907
This commit is contained in:
@@ -182,7 +182,7 @@ class MailMail(models.Model):
|
||||
messages = notifications.mapped('mail_message_id').filtered(lambda m: m.is_thread_message())
|
||||
# TDE TODO: could be great to notify message-based, not notifications-based, to lessen number of notifs
|
||||
messages._notify_message_notification_update() # notify user that we have a failure
|
||||
if not failure_type or failure_type == 'RECIPIENT': # if we have another error, we want to keep the mail.
|
||||
if not failure_type or failure_type in ['RECIPIENT', 'mail_email_missing']: # if we have another error, we want to keep the mail.
|
||||
mail_to_delete_ids = [mail.id for mail in self if mail.auto_delete]
|
||||
self.browse(mail_to_delete_ids).sudo().unlink()
|
||||
return True
|
||||
@@ -376,6 +376,8 @@ class MailMail(models.Model):
|
||||
|
||||
# build an RFC2822 email.message.Message object and send it without queuing
|
||||
res = None
|
||||
# TDE note: could be great to pre-detect missing to/cc and skip sending it
|
||||
# to go directly to failed state update
|
||||
for email in email_list:
|
||||
msg = IrMailServer.build_email(
|
||||
email_from=mail.email_from,
|
||||
@@ -401,7 +403,11 @@ class MailMail(models.Model):
|
||||
processing_pid = None
|
||||
except AssertionError as error:
|
||||
if str(error) == IrMailServer.NO_VALID_RECIPIENT:
|
||||
failure_type = "RECIPIENT"
|
||||
# if we have a list of void emails for email_list -> email missing, otherwise generic email failure
|
||||
if not email.get('email_to') and failure_type != "RECIPIENT":
|
||||
failure_type = "mail_email_missing"
|
||||
else:
|
||||
failure_type = "RECIPIENT"
|
||||
# No valid recipient found for this particular
|
||||
# mail item -> ignore error to avoid blocking
|
||||
# delivery to next recipients, if any. If this is
|
||||
|
||||
@@ -27,37 +27,37 @@ class Message(models.Model):
|
||||
* notification_status
|
||||
'ready', 'sent', 'bounce', 'exception', 'canceled'
|
||||
* notification_type
|
||||
'inbox', 'email', 'sms' (SMS addon), 'snail' (snailmail addon)
|
||||
'inbox', 'email', 'sms' (SMS addon), 'snail' (snailmail addon)
|
||||
* failure_type
|
||||
# mail
|
||||
"SMTP", "RECIPIENT", "BOUNCE", "UNKNOWN"
|
||||
# sms (SMS addon)
|
||||
'sms_number_missing', 'sms_number_format', 'sms_credit',
|
||||
'sms_server', 'sms_acc'
|
||||
# snailmail (snailmail addon)
|
||||
'sn_credit', 'sn_trial', 'sn_price', 'sn_fields',
|
||||
'sn_format', 'sn_error'
|
||||
# mail
|
||||
"SMTP", "RECIPIENT", "BOUNCE", "UNKNOWN", "mail_email_missing"
|
||||
# sms (SMS addon)
|
||||
'sms_number_missing', 'sms_number_format', 'sms_credit',
|
||||
'sms_server', 'sms_acc'
|
||||
# snailmail (snailmail addon)
|
||||
'sn_credit', 'sn_trial', 'sn_price', 'sn_fields',
|
||||
'sn_format', 'sn_error'
|
||||
|
||||
* mail.mail
|
||||
* state
|
||||
'outgoing', 'sent', 'received', 'exception', 'cancel'
|
||||
'outgoing', 'sent', 'received', 'exception', 'cancel'
|
||||
* failure_reason: text
|
||||
|
||||
* sms.sms (SMS addon)
|
||||
* state
|
||||
'outgoing', 'sent', 'error', 'canceled'
|
||||
'outgoing', 'sent', 'error', 'canceled'
|
||||
* error_code
|
||||
'sms_number_missing', 'sms_number_format', 'sms_credit',
|
||||
'sms_server', 'sms_acc',
|
||||
# mass mode specific codes
|
||||
'sms_blacklist', 'sms_duplicate'
|
||||
'sms_number_missing', 'sms_number_format', 'sms_credit',
|
||||
'sms_server', 'sms_acc',
|
||||
# mass mode specific codes
|
||||
'sms_blacklist', 'sms_duplicate'
|
||||
|
||||
* snailmail.letter (snailmail addon)
|
||||
* state
|
||||
'pending', 'sent', 'error', 'canceled'
|
||||
'pending', 'sent', 'error', 'canceled'
|
||||
* error_code
|
||||
'CREDIT_ERROR', 'TRIAL_ERROR', 'NO_PRICE_AVAILABLE', 'FORMAT_ERROR',
|
||||
'UNKNOWN_ERROR',
|
||||
'CREDIT_ERROR', 'TRIAL_ERROR', 'NO_PRICE_AVAILABLE', 'FORMAT_ERROR',
|
||||
'UNKNOWN_ERROR',
|
||||
|
||||
See ``mailing.trace`` model in mass_mailing application for mailing trace
|
||||
information.
|
||||
|
||||
@@ -38,6 +38,7 @@ class MailNotification(models.Model):
|
||||
("RECIPIENT", "Invalid email address"),
|
||||
("BOUNCE", "Email address rejected by destination"),
|
||||
("UNKNOWN", "Unknown error"),
|
||||
("mail_email_missing", "Missing email address"),
|
||||
], string='Failure type')
|
||||
failure_reason = fields.Text('Failure reason', copy=False)
|
||||
|
||||
|
||||
@@ -17,12 +17,14 @@ class MailingTrace(models.Model):
|
||||
'exception', 'bounced', 'ignored'
|
||||
* failure_type
|
||||
# mass_mailing
|
||||
"SMTP", "RECIPIENT", "BOUNCE", "UNKNOWN"
|
||||
"SMTP", "RECIPIENT", "BOUNCE", "UNKNOWN", "mail_email_missing"
|
||||
# mass mailing mass mode specific codes
|
||||
"mail_bl", "mail_optout", "mail_dup"
|
||||
# mass_mailing_sms
|
||||
'sms_number_missing', 'sms_number_format', 'sms_credit',
|
||||
'sms_server', 'sms_acc'
|
||||
# mass_mailing_sms mass mode specific codes
|
||||
'sms_blacklist', 'sms_duplicate'
|
||||
'sms_blacklist', 'sms_duplicate', 'sms_optout',
|
||||
* ignored:
|
||||
* mail: set in get_mail_values in composer, if email is blacklisted
|
||||
(mail) or in opt_out / seen list (mass_mailing) or email_to is void
|
||||
@@ -30,13 +32,10 @@ class MailingTrace(models.Model):
|
||||
* sms: set in _prepare_mass_sms_trace_values in composer if sms is
|
||||
in cancel state; either blacklisted (sms) or in opt_out / seen list
|
||||
(sms);
|
||||
* difference: void mail -> cancel -> ignore, void sms -> error
|
||||
sms_number_missing -> exception
|
||||
* difference: invalid mail -> cancel -> ignore, invalid sms -> error
|
||||
sms_number_format -> sent + bounce;
|
||||
* void mail / void sms number -> error (mail_missing, sms_number_missing)
|
||||
* invalid mail / invalid sms number -> error (RECIPIENT, sms_number_format)
|
||||
* exception: set in _postprocess_sent_message (_postprocess_iap_sent_sms)
|
||||
if mail (sms) not sent with failure type, reset if sent; also set for
|
||||
sms in _prepare_mass_sms_trace_values if void number
|
||||
if mail (sms) not sent with failure type, reset if sent;
|
||||
* sent: set in _postprocess_sent_message (_postprocess_iap_sent_sms) if
|
||||
mail (sms) sent
|
||||
* clicked: triggered by add_click
|
||||
@@ -100,6 +99,11 @@ class MailingTrace(models.Model):
|
||||
("RECIPIENT", "Invalid email address"),
|
||||
("BOUNCE", "Email address rejected by destination"),
|
||||
("UNKNOWN", "Unknown error"),
|
||||
("mail_email_missing", "Missing email address"),
|
||||
# mass mode
|
||||
("mail_bl", "Blacklisted Address"),
|
||||
("mail_optout", "Opted Out"),
|
||||
("mail_dup", "Duplicated Email"),
|
||||
], string='Failure type')
|
||||
state_update = fields.Datetime(compute="_compute_state", string='State Update',
|
||||
help='Last state update of the mail',
|
||||
|
||||
@@ -104,14 +104,15 @@ class MassMailCase(MailCase, MockLinkTracker):
|
||||
'MailTrace: email %s (recipient %s, state: %s, record: %s): found %s records (1 expected)' % (email, partner, state, record, len(recipient_trace))
|
||||
)
|
||||
self.assertTrue(bool(recipient_trace.mail_mail_id_int))
|
||||
if 'failure_type' in recipient_info or state in ('ignored', 'exception', 'canceled', 'bounced'):
|
||||
self.assertEqual(recipient_trace.failure_type, recipient_info['failure_type'])
|
||||
|
||||
if check_mail:
|
||||
if author is None:
|
||||
author = self.env.user.partner_id
|
||||
|
||||
# mail.mail specific values to check
|
||||
fields_values = {'mailing_id': mailing}
|
||||
if 'failure_type' in recipient_info:
|
||||
fields_values['failure_type'] = recipient_info['failure_type']
|
||||
|
||||
# specific for partner: email_formatted is used
|
||||
if partner:
|
||||
|
||||
@@ -276,7 +276,7 @@ class TestMassMailFeatures(MassMailCommon, CronMixinCase):
|
||||
|
||||
self.assertMailTraces(
|
||||
[{'partner': partner_a},
|
||||
{'partner': partner_b, 'state': 'ignored'}],
|
||||
{'partner': partner_b, 'state': 'ignored', 'failure_type': 'mail_bl'}],
|
||||
mailing, partner_a + partner_b, check_mail=True
|
||||
)
|
||||
|
||||
|
||||
@@ -57,18 +57,38 @@ class MailComposeMessage(models.TransientModel):
|
||||
seen_list = self._context.get('mass_mailing_seen_list')
|
||||
mass_mail_layout = self.env.ref('mass_mailing.mass_mailing_mail_layout', raise_if_not_found=False)
|
||||
for res_id in res_ids:
|
||||
if mail_values.get('body_html') and mass_mail_layout:
|
||||
mail_values['body_html'] = mass_mail_layout._render({'body': mail_values['body_html']}, engine='ir.qweb', minimal_qcontext=True)
|
||||
|
||||
mail_values = res[res_id]
|
||||
if mail_values.get('email_to'):
|
||||
mail_to = tools.email_normalize(mail_values['email_to'])
|
||||
mail_to = mail_values['email_to']
|
||||
else:
|
||||
partner_id = (mail_values.get('recipient_ids') or [(False, '')])[0][1]
|
||||
mail_to = tools.email_normalize(partners_email.get(partner_id))
|
||||
if (opt_out_list and mail_to in opt_out_list) or (seen_list and mail_to in seen_list) \
|
||||
or (not mail_to or not email_re.findall(mail_to)):
|
||||
# prevent sending to blocked addresses that were included by mistake
|
||||
mail_to = partners_email.get(partner_id)
|
||||
mail_to_normalized = tools.email_normalize(mail_to)
|
||||
|
||||
# prevent sending to blocked addresses that were included by mistake
|
||||
# blacklisted or optout or duplicate -> cancel
|
||||
error_code = False
|
||||
if mail_values.get('state') == 'cancel':
|
||||
error_code = 'mail_bl'
|
||||
elif opt_out_list and mail_to in opt_out_list:
|
||||
mail_values['state'] = 'cancel'
|
||||
error_code = 'mail_optout'
|
||||
elif seen_list and mail_to in seen_list:
|
||||
mail_values['state'] = 'cancel'
|
||||
error_code = 'mail_dup'
|
||||
# void of falsey values -> error
|
||||
elif not mail_to:
|
||||
mail_values['state'] = 'cancel'
|
||||
error_code = 'mail_email_missing'
|
||||
elif not mail_to_normalized or not email_re.findall(mail_to):
|
||||
mail_values['state'] = 'cancel'
|
||||
error_code = "RECIPIENT"
|
||||
elif seen_list is not None:
|
||||
seen_list.add(mail_to)
|
||||
|
||||
trace_vals = {
|
||||
'model': self.model,
|
||||
'res_id': res_id,
|
||||
@@ -76,11 +96,14 @@ class MailComposeMessage(models.TransientModel):
|
||||
# if mail_to is void, keep falsy values to allow searching / debugging traces
|
||||
'email': mail_to or mail_values.get('email_to'),
|
||||
}
|
||||
if mail_values.get('body_html') and mass_mail_layout:
|
||||
mail_values['body_html'] = mass_mail_layout._render({'body': mail_values['body_html']}, engine='ir.qweb', minimal_qcontext=True)
|
||||
# propagate ignored state to trace when still-born
|
||||
# propagate failed states to trace when still-born
|
||||
if mail_values.get('state') == 'cancel':
|
||||
trace_vals['ignored'] = fields.Datetime.now()
|
||||
elif mail_values.get('state') == 'exception':
|
||||
trace_vals['exception'] = fields.Datetime.now()
|
||||
if error_code:
|
||||
trace_vals['failure_type'] = error_code
|
||||
|
||||
mail_values.update({
|
||||
'mailing_id': mass_mailing.id,
|
||||
'mailing_trace_ids': [(0, 0, trace_vals)],
|
||||
|
||||
@@ -36,6 +36,7 @@ class MailingTrace(models.Model):
|
||||
# mass mode specific codes
|
||||
('sms_blacklist', 'Blacklisted'),
|
||||
('sms_duplicate', 'Duplicate'),
|
||||
('sms_optout', 'Opted Out'),
|
||||
])
|
||||
|
||||
@api.model_create_multi
|
||||
|
||||
@@ -35,22 +35,19 @@ class SMSComposer(models.TransientModel):
|
||||
'sms_code': trace_code,
|
||||
}
|
||||
if sms_values['state'] == 'error':
|
||||
if sms_values['error_code'] == 'sms_number_format':
|
||||
trace_values['sent'] = fields.Datetime.now()
|
||||
trace_values['bounced'] = fields.Datetime.now()
|
||||
else:
|
||||
trace_values['exception'] = fields.Datetime.now()
|
||||
trace_values['failure_type'] = sms_values['error_code']
|
||||
trace_values['exception'] = fields.Datetime.now()
|
||||
elif sms_values['state'] == 'canceled':
|
||||
trace_values['failure_type'] = sms_values['error_code']
|
||||
trace_values['ignored'] = fields.Datetime.now()
|
||||
else:
|
||||
if self.mass_sms_allow_unsubscribe:
|
||||
sms_values['body'] = '%s\n%s' % (sms_values['body'] or '', _('STOP SMS : %s', self._get_unsubscribe_url(record.id, trace_code, sms_values['number'])))
|
||||
return trace_values
|
||||
|
||||
def _get_blacklist_record_ids(self, records, recipients_info):
|
||||
""" Consider opt-outed contact as being blacklisted for that specific
|
||||
mailing. """
|
||||
res = super(SMSComposer, self)._get_blacklist_record_ids(records, recipients_info)
|
||||
def _get_optout_record_ids(self, records, recipients_info):
|
||||
""" Fetch opt-out records based on mailing. """
|
||||
res = super(SMSComposer, self)._get_optout_record_ids(records, recipients_info)
|
||||
if self.mailing_id:
|
||||
optout_res_ids = self.mailing_id._get_opt_out_list_sms()
|
||||
res += optout_res_ids
|
||||
|
||||
@@ -42,6 +42,7 @@ class SmsSms(models.Model):
|
||||
# mass mode specific codes
|
||||
('sms_blacklist', 'Blacklisted'),
|
||||
('sms_duplicate', 'Duplicate'),
|
||||
('sms_optout', 'Opted Out'),
|
||||
], copy=False)
|
||||
|
||||
def send(self, delete_all=False, auto_commit=False, raise_exception=False):
|
||||
|
||||
@@ -273,6 +273,11 @@ class SendSMS(models.TransientModel):
|
||||
return [r.id for r in records if recipients_info[r.id]['sanitized'] in bl_numbers]
|
||||
return []
|
||||
|
||||
def _get_optout_record_ids(self, records, recipients_info):
|
||||
""" Compute opt-outed contacts, not necessarily blacklisted. Void by default
|
||||
as no opt-out mechanism exist in SMS, see SMS Marketing. """
|
||||
return []
|
||||
|
||||
def _get_done_record_ids(self, records, recipients_info):
|
||||
""" Get a list of already-done records. Order of record set is used to
|
||||
spot duplicates so pay attention to it if necessary. """
|
||||
@@ -300,6 +305,7 @@ class SendSMS(models.TransientModel):
|
||||
all_bodies = self._prepare_body_values(records)
|
||||
all_recipients = self._prepare_recipient_values(records)
|
||||
blacklist_ids = self._get_blacklist_record_ids(records, all_recipients)
|
||||
optout_ids = self._get_optout_record_ids(records, all_recipients)
|
||||
done_ids = self._get_done_record_ids(records, all_recipients)
|
||||
|
||||
result = {}
|
||||
@@ -309,11 +315,14 @@ class SendSMS(models.TransientModel):
|
||||
if sanitized and record.id in blacklist_ids:
|
||||
state = 'canceled'
|
||||
error_code = 'sms_blacklist'
|
||||
elif sanitized and record.id in optout_ids:
|
||||
state = 'canceled'
|
||||
error_code = 'sms_optout'
|
||||
elif sanitized and record.id in done_ids:
|
||||
state = 'canceled'
|
||||
error_code = 'sms_duplicate'
|
||||
elif not sanitized:
|
||||
state = 'error'
|
||||
state = 'canceled'
|
||||
error_code = 'sms_number_format' if recipients['number'] else 'sms_number_missing'
|
||||
else:
|
||||
state = 'outgoing'
|
||||
|
||||
@@ -11,6 +11,7 @@ from odoo.tests import tagged
|
||||
|
||||
@tagged('mass_mailing')
|
||||
class TestMassMailing(TestMailFullCommon):
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mailing_w_blacklist_opt_out(self):
|
||||
@@ -47,18 +48,23 @@ class TestMassMailing(TestMailFullCommon):
|
||||
# opt-out: ignored (cancel mail)
|
||||
if recipient in recipients[1] | recipients[2]:
|
||||
recipient_info['state'] = 'ignored'
|
||||
recipient_info['failure_type'] = "mail_optout"
|
||||
# blacklisted: ignored (cancel mail)
|
||||
elif recipient in recipients[3] | recipients[4]:
|
||||
recipient_info['state'] = 'ignored'
|
||||
recipient_info['failure_type'] = "mail_bl"
|
||||
# duplicates: ignored (cancel mail)
|
||||
elif recipient == recipient_dup_1:
|
||||
recipient_info['state'] = 'ignored'
|
||||
# void: ignored (cancel mail)
|
||||
recipient_info['failure_type'] = "mail_dup"
|
||||
# void: error (failed mail)
|
||||
elif recipient == recipient_void_1:
|
||||
recipient_info['state'] = 'ignored'
|
||||
# falsy: ignored (cancel mail)
|
||||
recipient_info['failure_type'] = "mail_email_missing"
|
||||
# falsy: error (failed mail)
|
||||
elif recipient == recipient_falsy_1:
|
||||
recipient_info['state'] = 'ignored'
|
||||
recipient_info['failure_type'] = "RECIPIENT"
|
||||
recipient_info['email'] = recipient.email_from # normalized is False but email should be falsymail
|
||||
else:
|
||||
email = self._find_sent_mail_wemail(recipient.email_normalized)
|
||||
@@ -111,4 +117,5 @@ class TestMassMailing(TestMailFullCommon):
|
||||
check_mail=True,)
|
||||
|
||||
# sent: 13, 2 bl, 2 opt-out, 3 invalid -> 6 remaining
|
||||
self.assertMailingStatistics(mailing, expected=13, delivered=6, sent=6, ignored=7)
|
||||
# ignored: 2 bl + 2 optout + 2 invalid + 1 duplicate; failed: 0
|
||||
self.assertMailingStatistics(mailing, expected=13, delivered=6, sent=6, ignored=7, failed=0)
|
||||
|
||||
@@ -183,14 +183,14 @@ class TestMassSMSInternals(TestMassSMSCommon):
|
||||
# missing number
|
||||
self.assertSMSTraces(
|
||||
[{'partner': self.env['res.partner'], 'number': False,
|
||||
'content': 'Dear %s this is a mass SMS.' % void_record.display_name, 'state': 'exception',
|
||||
'content': 'Dear %s this is a mass SMS.' % void_record.display_name, 'state': 'ignored',
|
||||
'failure_type': 'sms_number_missing'}],
|
||||
self.mailing, void_record,
|
||||
)
|
||||
# wrong values
|
||||
self.assertSMSTraces(
|
||||
[{'partner': self.env['res.partner'], 'number': record.phone_nbr,
|
||||
'content': 'Dear %s this is a mass SMS.' % record.display_name, 'state': 'bounced',
|
||||
'content': 'Dear %s this is a mass SMS.' % record.display_name, 'state': 'ignored',
|
||||
'failure_type': 'sms_number_format'}
|
||||
for record in falsy_record_1 + falsy_record_2],
|
||||
self.mailing, falsy_record_1 + falsy_record_2,
|
||||
@@ -395,8 +395,8 @@ class TestMassSMS(TestMassSMSCommon):
|
||||
mailing.action_send_sms()
|
||||
|
||||
self.assertSMSTraces(
|
||||
[{'number': '+32456000000', 'state': 'ignored', 'failure_type': 'sms_blacklist'}, # TDE FIXME: should be opt_out
|
||||
{'number': '+32456000101', 'state': 'ignored', 'failure_type': 'sms_blacklist'}, # TDE FIXME: should be opt_out
|
||||
[{'number': '+32456000000', 'state': 'ignored', 'failure_type': 'sms_optout'},
|
||||
{'number': '+32456000101', 'state': 'ignored', 'failure_type': 'sms_optout'},
|
||||
{'number': '+32456000202', 'state': 'sent'},
|
||||
{'number': '+32456000303', 'state': 'sent'},
|
||||
{'number': '+32456000404', 'state': 'ignored', 'failure_type': 'sms_blacklist'}],
|
||||
|
||||
@@ -78,6 +78,6 @@ class TestAutoBlacklist(common.TestMassMailCommon):
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
new_mailing._process_mass_mailing_queue()
|
||||
self.assertMailTraces(
|
||||
[{'email': 'test.record.00@test.example.com', 'state': 'ignored'}],
|
||||
[{'email': 'test.record.00@test.example.com', 'state': 'ignored', 'failure_type': 'mail_bl'}],
|
||||
new_mailing, target, check_mail=True
|
||||
)
|
||||
|
||||
@@ -245,8 +245,8 @@ class TestMassMailing(TestMassMailCommon):
|
||||
[{'email': 'test.record.00@test.example.com'},
|
||||
{'email': 'test.record.01@test.example.com'},
|
||||
{'email': 'test.record.02@test.example.com'},
|
||||
{'email': 'test.record.03@test.example.com', 'state': 'ignored'},
|
||||
{'email': 'test.record.04@test.example.com', 'state': 'ignored'}],
|
||||
{'email': 'test.record.03@test.example.com', 'state': 'ignored', 'failure_type': 'mail_bl'},
|
||||
{'email': 'test.record.04@test.example.com', 'state': 'ignored', 'failure_type': 'mail_bl'}],
|
||||
mailing, recipients, check_mail=True
|
||||
)
|
||||
self.assertEqual(mailing.ignored, 2)
|
||||
@@ -271,11 +271,11 @@ class TestMassMailing(TestMassMailCommon):
|
||||
mailing._process_mass_mailing_queue()
|
||||
|
||||
self.assertMailTraces(
|
||||
[{'email': 'test.record.00@test.example.com', 'state': 'ignored'},
|
||||
{'email': 'test.record.01@test.example.com', 'state': 'ignored'},
|
||||
[{'email': 'test.record.00@test.example.com', 'state': 'ignored', 'failure_type': 'mail_optout'},
|
||||
{'email': 'test.record.01@test.example.com', 'state': 'ignored', 'failure_type': 'mail_optout'},
|
||||
{'email': 'test.record.02@test.example.com'},
|
||||
{'email': 'test.record.03@test.example.com'},
|
||||
{'email': 'test.record.04@test.example.com', 'state': 'ignored'}],
|
||||
{'email': 'test.record.04@test.example.com', 'state': 'ignored', 'failure_type': 'mail_bl'}],
|
||||
mailing, recipients, check_mail=True
|
||||
)
|
||||
self.assertEqual(mailing.ignored, 3)
|
||||
@@ -332,10 +332,10 @@ class TestMassMailing(TestMassMailCommon):
|
||||
|
||||
self.assertMailTraces(
|
||||
[{'email': 'test@test.example.com', 'state': 'sent'},
|
||||
{'email': 'test@test.example.com', 'state': 'ignored'},
|
||||
{'email': 'test@test.example.com', 'state': 'ignored', 'failure_type': 'mail_dup'},
|
||||
{'email': 'test3@test.example.com'},
|
||||
{'email': 'test4@test.example.com'},
|
||||
{'email': 'test5@test.example.com', 'state': 'ignored'}],
|
||||
{'email': 'test5@test.example.com', 'state': 'ignored', 'failure_type': 'mail_optout'}],
|
||||
mailing,
|
||||
mailing_contact_1 + mailing_contact_2 + mailing_contact_3 + mailing_contact_4 + mailing_contact_5,
|
||||
check_mail=True
|
||||
|
||||
Reference in New Issue
Block a user