def test_delete_list_with_autoresponse_record(self): # Ensure that mailing lists with auto-response sets can be deleted. In # issue #115, this fails under PostgreSQL, but not SQLite. list_manager = getUtility(IListManager) user_manager = getUtility(IUserManager) mlist = create_list('*****@*****.**') address = user_manager.create_address('*****@*****.**') autoresponse_set = IAutoResponseSet(mlist) autoresponse_set.response_sent(address, Response.hold) list_manager.delete(mlist) self.assertIsNone(list_manager.get('*****@*****.**'))
def test_delete_address(self): address = self._usermanager.create_address('*****@*****.**') address.verified_on = now() # Subscribe the address to a list. mlist = create_list('*****@*****.**') mlist.subscribe(address) # Set an autorespond record. response_set = IAutoResponseSet(mlist) response_set.response_sent(address, Response.hold) # And add a digest record. mlist.send_one_last_digest_to(address, DeliveryMode.plaintext_digests) # Now delete the address. self._usermanager.delete_address(address) # Flush the database to provoke an integrity error on PostgreSQL # without the fix. config.db.store.flush() self.assertIsNone(self._usermanager.get_address('*****@*****.**'))
def autorespond_to_sender(mlist, sender, language=None): """Should Mailman automatically respond to this sender? :param mlist: The mailing list. :type mlist: `IMailingList`. :param sender: The sender's email address. :type sender: string :param language: Optional language. :type language: `ILanguage` or None :return: True if an automatic response should be sent, otherwise False. If an automatic response is not sent, a message is sent indicating that, er no more will be sent today. :rtype: bool """ if language is None: language = mlist.preferred_language max_autoresponses_per_day = int(config.mta.max_autoresponses_per_day) if max_autoresponses_per_day == 0: # Unlimited. return True # Get an IAddress from an email address. user_manager = getUtility(IUserManager) address = user_manager.get_address(sender) if address is None: address = user_manager.create_address(sender) response_set = IAutoResponseSet(mlist) todays_count = response_set.todays_count(address, Response.hold) if todays_count < max_autoresponses_per_day: # This person has not reached their automatic response limit, so it's # okay to send a response. response_set.response_sent(address, Response.hold) return True elif todays_count == max_autoresponses_per_day: # The last one we sent was the last one we should send today. Instead # of sending an automatic response, send them the "no more today" # message. log.info('hold autoresponse limit hit: %s', sender) response_set.response_sent(address, Response.hold) # Send this notification message instead. text = make( 'nomoretoday.txt', language=language.code, sender=sender, listname=mlist.fqdn_listname, count=todays_count, owneremail=mlist.owner_address, ) with _.using(language.code): msg = UserNotification( sender, mlist.owner_address, _('Last autoresponse notification for today'), text, lang=language) msg.send(mlist) return False else: # We've sent them everything we're going to send them today. log.info('Automatic response limit discard: %s', sender) return False
def test_max_autoresponses_per_day(self): # The last one we sent was the last one we should send today. Instead # of sending an automatic response, send them the "no more today" # message. config.push('max-1', """ [mta] max_autoresponses_per_day: 1 """) # Simulate a response having been sent to an address already. anne = getUtility(IUserManager).create_address('*****@*****.**') response_set = IAutoResponseSet(self._mlist) response_set.response_sent(anne, Response.hold) # Trigger the sending of a "last response for today" using the default # language (i.e. the mailing list's preferred language). autorespond_to_sender(self._mlist, '*****@*****.**') # So first, there should be one more hold response sent to the user. self.assertEqual(response_set.todays_count(anne, Response.hold), 2) # And the virgin queue should have the message in it. messages = get_queue_messages('virgin') self.assertEqual(len(messages), 1) # Remove the variable headers. message = messages[0].msg self.assertTrue('message-id' in message) del message['message-id'] self.assertTrue('date' in message) del message['date'] self.eq(messages[0].msg.as_string(), """\ MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Subject: Last autoresponse notification for today From: [email protected] To: [email protected] Precedence: bulk We have received a message from your address <*****@*****.**> requesting an automated response from the [email protected] mailing list. The number we have seen today: 1. In order to avoid problems such as mail loops between email robots, we will not be sending you any further responses today. Please try again tomorrow. If you believe this message is in error, or if you have any questions, please contact the list owner at [email protected].""")
def test_max_autoresponses_per_day(self): # The last one we sent was the last one we should send today. Instead # of sending an automatic response, send them the "no more today" # message. Start by simulating a response having been sent to an # address already. anne = getUtility(IUserManager).create_address('*****@*****.**') response_set = IAutoResponseSet(self._mlist) response_set.response_sent(anne, Response.hold) # Trigger the sending of a "last response for today" using the default # language (i.e. the mailing list's preferred language). autorespond_to_sender(self._mlist, '*****@*****.**') # So first, there should be one more hold response sent to the user. self.assertEqual(response_set.todays_count(anne, Response.hold), 2) # And the virgin queue should have the message in it. messages = get_queue_messages('virgin') self.assertEqual(len(messages), 1) # Remove the variable headers. message = messages[0].msg self.assertIn('message-id', message) del message['message-id'] self.assertIn('date', message) del message['date'] self.assertMultiLineEqual( messages[0].msg.as_string(), """\ MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Subject: Last autoresponse notification for today From: [email protected] To: [email protected] Precedence: bulk We have received a message from your address <*****@*****.**> requesting an automated response from the [email protected] mailing list. The number we have seen today: 1. In order to avoid problems such as mail loops between email robots, we will not be sending you any further responses today. Please try again tomorrow. If you believe this message is in error, or if you have any questions, please contact the list owner at [email protected].""")
def autorespond_to_sender(mlist, sender, language=None): """Should Mailman automatically respond to this sender? :param mlist: The mailing list. :type mlist: `IMailingList`. :param sender: The sender's email address. :type sender: string :param language: Optional language. :type language: `ILanguage` or None :return: True if an automatic response should be sent, otherwise False. If an automatic response is not sent, a message is sent indicating that, er no more will be sent today. :rtype: bool """ if language is None: language = mlist.preferred_language max_autoresponses_per_day = int(config.mta.max_autoresponses_per_day) if max_autoresponses_per_day == 0: # Unlimited. return True # Get an IAddress from an email address. user_manager = getUtility(IUserManager) address = user_manager.get_address(sender) if address is None: address = user_manager.create_address(sender) response_set = IAutoResponseSet(mlist) todays_count = response_set.todays_count(address, Response.hold) if todays_count < max_autoresponses_per_day: # This person has not reached their automatic response limit, so it's # okay to send a response. response_set.response_sent(address, Response.hold) return True elif todays_count == max_autoresponses_per_day: # The last one we sent was the last one we should send today. Instead # of sending an automatic response, send them the "no more today" # message. log.info('hold autoresponse limit hit: %s', sender) response_set.response_sent(address, Response.hold) # Send this notification message instead. text = make('nomoretoday.txt', language=language.code, sender=sender, listname=mlist.fqdn_listname, count=todays_count, owneremail=mlist.owner_address, ) with _.using(language.code): msg = UserNotification( sender, mlist.owner_address, _('Last autoresponse notification for today'), text, lang=language) msg.send(mlist) return False else: # We've sent them everything we're going to send them today. log.info('Automatic response limit discard: %s', sender) return False
def process(self, mlist, msg, msgdata): """See `IHandler`.""" # There are several cases where the replybot is short-circuited: # * the original message has an "X-Ack: No" header # * the message has a Precedence header with values bulk, junk, or # list, and there's no explicit "X-Ack: yes" header # * the message metadata has a true 'noack' key ack = msg.get('x-ack', '').lower() if ack == 'no' or msgdata.get('noack'): return precedence = msg.get('precedence', '').lower() if ack != 'yes' and precedence in ('bulk', 'junk', 'list'): return # Check to see if the list is even configured to autorespond to this # email message. Note: the incoming message processors should set the # destination key in the message data. if msgdata.get('to_owner'): if mlist.autorespond_owner is ResponseAction.none: return response_type = Response.owner response_text = mlist.autoresponse_owner_text elif msgdata.get('to_request'): if mlist.autorespond_requests is ResponseAction.none: return response_type = Response.command response_text = mlist.autoresponse_request_text elif msgdata.get('to_list'): if mlist.autorespond_postings is ResponseAction.none: return response_type = Response.postings response_text = mlist.autoresponse_postings_text else: # There are no automatic responses for any other destination. return # Now see if we're in the grace period for this sender. grace_period # = 0 means always automatically respond, as does an "X-Ack: yes" # header (useful for debugging). response_set = IAutoResponseSet(mlist) user_manager = getUtility(IUserManager) address = user_manager.get_address(msg.sender) if address is None: address = user_manager.create_address(msg.sender) grace_period = mlist.autoresponse_grace_period if grace_period > ALWAYS_REPLY and ack != 'yes': last = response_set.last_response(address, response_type) if last is not None and last.date_sent + grace_period > today(): return # Okay, we know we're going to respond to this sender, craft the # message, send it, and update the database. display_name = mlist.display_name subject = _( 'Auto-response for your message to the "$display_name" ' 'mailing list') # Do string interpolation into the autoresponse text d = dict(list_name = mlist.list_name, display_name = display_name, listurl = mlist.script_url('listinfo'), requestemail = mlist.request_address, owneremail = mlist.owner_address, ) # Interpolation and Wrap the response text. text = wrap(expand(response_text, d)) outmsg = UserNotification(msg.sender, mlist.bounces_address, subject, text, mlist.preferred_language) outmsg['X-Mailer'] = _('The Mailman Replybot') # prevent recursions and mail loops! outmsg['X-Ack'] = 'No' outmsg.send(mlist) response_set.response_sent(address, response_type)
def process(self, mlist, msg, msgdata): """See `IHandler`.""" # There are several cases where the replybot is short-circuited: # * the original message has an "X-Ack: No" header # * the message has a Precedence header with values bulk, junk, or # list, and there's no explicit "X-Ack: yes" header # * the message metadata has a true 'noack' key ack = msg.get('x-ack', '').lower() if ack == 'no' or msgdata.get('noack'): return precedence = msg.get('precedence', '').lower() if ack != 'yes' and precedence in ('bulk', 'junk', 'list'): return # Check to see if the list is even configured to autorespond to this # email message. Note: the incoming message processors should set the # destination key in the message data. if msgdata.get('to_owner'): if mlist.autorespond_owner is ResponseAction.none: return response_type = Response.owner response_text = mlist.autoresponse_owner_text elif msgdata.get('to_request'): if mlist.autorespond_requests is ResponseAction.none: return response_type = Response.command response_text = mlist.autoresponse_request_text elif msgdata.get('to_list'): if mlist.autorespond_postings is ResponseAction.none: return response_type = Response.postings response_text = mlist.autoresponse_postings_text else: # There are no automatic responses for any other destination. return # Now see if we're in the grace period for this sender. grace_period # = 0 means always automatically respond, as does an "X-Ack: yes" # header (useful for debugging). response_set = IAutoResponseSet(mlist) user_manager = getUtility(IUserManager) address = user_manager.get_address(msg.sender) if address is None: address = user_manager.create_address(msg.sender) grace_period = mlist.autoresponse_grace_period if grace_period > ALWAYS_REPLY and ack != 'yes': last = response_set.last_response(address, response_type) if last is not None and last.date_sent + grace_period > today(): return # Okay, we know we're going to respond to this sender, craft the # message, send it, and update the database. display_name = mlist.display_name subject = _('Auto-response for your message to the "$display_name" ' 'mailing list') # Do string interpolation into the autoresponse text d = dict( list_name=mlist.list_name, display_name=display_name, requestemail=mlist.request_address, owneremail=mlist.owner_address, ) # Interpolation and Wrap the response text. text = wrap(expand(response_text, mlist, d)) outmsg = UserNotification(msg.sender, mlist.bounces_address, subject, text, mlist.preferred_language) outmsg['X-Mailer'] = _('The Mailman Replybot') # prevent recursions and mail loops! outmsg['X-Ack'] = 'No' outmsg.send(mlist) response_set.response_sent(address, response_type)