1==========================
2Automatic response handler
3==========================
4
5Mailman has a autoreply handler that sends automatic responses to messages it
6receives on its posting address, owner address, or robot address.  Automatic
7responses are subject to various conditions, such as headers in the original
8message or the amount of time since the last auto-response.
9
10    >>> mlist = create_list('_xtest@example.com')
11    >>> mlist.display_name = 'XTest'
12
13
14Basic automatic responding
15==========================
16
17Basic automatic responding occurs when the list is set up to respond to either
18its ``-owner`` address, its ``-request`` address, or to the posting address,
19and a message is sent to one of these addresses.  A mailing list also has an
20automatic response grace period which specifies how much time must pass before
21a second response will be sent, with 0 meaning "there is no grace period".
22::
23
24    >>> from datetime import timedelta
25    >>> from mailman.interfaces.autorespond import ResponseAction
26
27    >>> mlist.autorespond_owner = ResponseAction.respond_and_continue
28    >>> mlist.autoresponse_grace_period = timedelta()
29    >>> mlist.autoresponse_owner_text = 'owner autoresponse text'
30
31    >>> msg = message_from_string("""\
32    ... From: aperson@example.com
33    ... To: _xtest-owner@example.com
34    ...
35    ... help
36    ... """)
37
38The preceding message to the mailing list's owner will trigger an automatic
39response.
40::
41
42    >>> from mailman.testing.helpers import get_queue_messages
43
44    >>> handler = config.handlers['replybot']
45    >>> handler.process(mlist, msg, dict(to_owner=True))
46    >>> messages = get_queue_messages('virgin')
47    >>> len(messages)
48    1
49
50    >>> dump_msgdata(messages[0].msgdata)
51    _parsemsg           : False
52    listid              : _xtest.example.com
53    nodecorate          : True
54    recipients          : {'aperson@example.com'}
55    reduced_list_headers: True
56    version             : 3
57
58    >>> print(messages[0].msg.as_string())
59    MIME-Version: 1.0
60    Content-Type: text/plain; charset="us-ascii"
61    Content-Transfer-Encoding: 7bit
62    Subject: Auto-response for your message to the "XTest" mailing list
63    From: _xtest-bounces@example.com
64    To: aperson@example.com
65    X-Mailer: The Mailman Replybot
66    X-Ack: No
67    Message-ID: <...>
68    Date: ...
69    Precedence: bulk
70    <BLANKLINE>
71    owner autoresponse text
72
73
74Short circuiting
75================
76
77Several headers in the original message determine whether an automatic
78response should even be sent.  For example, if the message has an
79``X-Ack: No`` header, no auto-response is sent.
80::
81
82    >>> msg = message_from_string("""\
83    ... From: aperson@example.com
84    ... X-Ack: No
85    ...
86    ... help me
87    ... """)
88
89    >>> handler.process(mlist, msg, dict(to_owner=True))
90    >>> get_queue_messages('virgin')
91    []
92
93Mailman itself can suppress automatic responses for certain types of
94internally crafted messages, by setting the ``noack`` metadata key.
95::
96
97    >>> msg = message_from_string("""\
98    ... From: mailman@example.com
99    ...
100    ... help for you
101    ... """)
102
103    >>> handler.process(mlist, msg, dict(noack=True, to_owner=True))
104    >>> get_queue_messages('virgin')
105    []
106
107If there is a ``Precedence:`` header with any of the values ``bulk``,
108``junk``, or ``list``, then the automatic response is also suppressed.
109::
110
111    >>> msg = message_from_string("""\
112    ... From: asystem@example.com
113    ... Precedence: bulk
114    ...
115    ... hey!
116    ... """)
117
118    >>> handler.process(mlist, msg, dict(to_owner=True))
119    >>> get_queue_messages('virgin')
120    []
121
122    >>> msg.replace_header('precedence', 'junk')
123    >>> handler.process(mlist, msg, dict(to_owner=True))
124    >>> get_queue_messages('virgin')
125    []
126
127    >>> msg.replace_header('precedence', 'list')
128    >>> handler.process(mlist, msg, dict(to_owner=True))
129    >>> get_queue_messages('virgin')
130    []
131
132Unless the ``X-Ack:`` header has a value of ``yes``, in which case, the
133``Precedence`` header is ignored.
134::
135
136    >>> msg['X-Ack'] = 'yes'
137    >>> handler.process(mlist, msg, dict(to_owner=True))
138    >>> messages = get_queue_messages('virgin')
139    >>> len(messages)
140    1
141
142    >>> dump_msgdata(messages[0].msgdata)
143    _parsemsg           : False
144    listid              : _xtest.example.com
145    nodecorate          : True
146    recipients          : {'asystem@example.com'}
147    reduced_list_headers: True
148    version             : 3
149
150    >>> print(messages[0].msg.as_string())
151    MIME-Version: 1.0
152    Content-Type: text/plain; charset="us-ascii"
153    Content-Transfer-Encoding: 7bit
154    Subject: Auto-response for your message to the "XTest" mailing list
155    From: _xtest-bounces@example.com
156    To: asystem@example.com
157    X-Mailer: The Mailman Replybot
158    X-Ack: No
159    Message-ID: <...>
160    Date: ...
161    Precedence: bulk
162    <BLANKLINE>
163    owner autoresponse text
164
165
166Available auto-responses
167========================
168
169As shown above, a message sent to the ``-owner`` address will get an
170auto-response with the text set for owner responses.  Two other types of email
171will get auto-responses: those sent to the ``-request`` address...
172::
173
174    >>> mlist.autorespond_requests = ResponseAction.respond_and_continue
175    >>> mlist.autoresponse_request_text = 'robot autoresponse text'
176
177    >>> msg = message_from_string("""\
178    ... From: aperson@example.com
179    ... To: _xtest-request@example.com
180    ...
181    ... help me
182    ... """)
183
184    >>> handler.process(mlist, msg, dict(to_request=True))
185    >>> messages = get_queue_messages('virgin')
186    >>> len(messages)
187    1
188
189    >>> print(messages[0].msg.as_string())
190    MIME-Version: 1.0
191    Content-Type: text/plain; charset="us-ascii"
192    Content-Transfer-Encoding: 7bit
193    Subject: Auto-response for your message to the "XTest" mailing list
194    From: _xtest-bounces@example.com
195    To: aperson@example.com
196    X-Mailer: The Mailman Replybot
197    X-Ack: No
198    Message-ID: <...>
199    Date: ...
200    Precedence: bulk
201    <BLANKLINE>
202    robot autoresponse text
203
204...and those sent to the posting address.
205::
206
207    >>> mlist.autorespond_postings = ResponseAction.respond_and_continue
208    >>> mlist.autoresponse_postings_text = 'postings autoresponse text'
209
210    >>> msg = message_from_string("""\
211    ... From: aperson@example.com
212    ... To: _xtest@example.com
213    ...
214    ... help me
215    ... """)
216
217    >>> handler.process(mlist, msg, dict(to_list=True))
218    >>> messages = get_queue_messages('virgin')
219    >>> len(messages)
220    1
221
222    >>> print(messages[0].msg.as_string())
223    MIME-Version: 1.0
224    Content-Type: text/plain; charset="us-ascii"
225    Content-Transfer-Encoding: 7bit
226    Subject: Auto-response for your message to the "XTest" mailing list
227    From: _xtest-bounces@example.com
228    To: aperson@example.com
229    X-Mailer: The Mailman Replybot
230    X-Ack: No
231    Message-ID: <...>
232    Date: ...
233    Precedence: bulk
234    <BLANKLINE>
235    postings autoresponse text
236
237
238Grace periods
239=============
240
241Automatic responses have a grace period, during which no additional responses
242will be sent.  This is so as not to bombard the sender with responses.  The
243grace period is measured in days.
244
245    >>> mlist.autoresponse_grace_period = timedelta(days=10)
246
247When a response is sent to a person via any of the owner, request, or postings
248addresses, the response date is recorded.  The grace period is usually
249measured in days.
250
251    >>> msg = message_from_string("""\
252    ... From: bperson@example.com
253    ... To: _xtest-owner@example.com
254    ...
255    ... help
256    ... """)
257
258This is the first response to bperson, so it gets sent.
259
260    >>> handler.process(mlist, msg, dict(to_owner=True))
261    >>> len(get_queue_messages('virgin'))
262    1
263
264But with a grace period greater than zero, no subsequent response will be sent
265right now.
266
267    >>> handler.process(mlist, msg, dict(to_owner=True))
268    >>> len(get_queue_messages('virgin'))
269    0
270
271Fast forward 9 days and you still don't get a response.
272::
273
274    >>> from mailman.utilities.datetime import factory
275    >>> factory.fast_forward(days=9)
276
277    >>> handler.process(mlist, msg, dict(to_owner=True))
278    >>> len(get_queue_messages('virgin'))
279    0
280
281But tomorrow, the sender will get a new auto-response.
282
283    >>> factory.fast_forward()
284    >>> handler.process(mlist, msg, dict(to_owner=True))
285    >>> len(get_queue_messages('virgin'))
286    1
287
288Of course, everything works the same way for messages to the request
289address, even if the sender is the same person...
290::
291
292    >>> msg = message_from_string("""\
293    ... From: bperson@example.com
294    ... To: _xtest-request@example.com
295    ...
296    ... help
297    ... """)
298
299    >>> handler.process(mlist, msg, dict(to_request=True))
300    >>> len(get_queue_messages('virgin'))
301    1
302
303    >>> handler.process(mlist, msg, dict(to_request=True))
304    >>> len(get_queue_messages('virgin'))
305    0
306
307    >>> factory.fast_forward(days=9)
308    >>> handler.process(mlist, msg, dict(to_request=True))
309    >>> len(get_queue_messages('virgin'))
310    0
311
312    >>> factory.fast_forward()
313    >>> handler.process(mlist, msg, dict(to_request=True))
314    >>> len(get_queue_messages('virgin'))
315    1
316
317...and for messages to the posting address.
318::
319
320    >>> msg = message_from_string("""\
321    ... From: bperson@example.com
322    ... To: _xtest@example.com
323    ...
324    ... help
325    ... """)
326
327    >>> handler.process(mlist, msg, dict(to_list=True))
328    >>> len(get_queue_messages('virgin'))
329    1
330
331    >>> handler.process(mlist, msg, dict(to_list=True))
332    >>> len(get_queue_messages('virgin'))
333    0
334
335    >>> factory.fast_forward(days=9)
336    >>> handler.process(mlist, msg, dict(to_list=True))
337    >>> len(get_queue_messages('virgin'))
338    0
339
340    >>> factory.fast_forward()
341    >>> handler.process(mlist, msg, dict(to_list=True))
342    >>> len(get_queue_messages('virgin'))
343    1
344