Merge branch 'weblate-gnu-mailman-mailman' into 'master'
[mailman.git] / src / mailman / rest / docs / listconf.rst
blob4ab442bdfff1e7c193af946bd9c14d5103f8ce23
1 ==========================
2 Mailing list configuration
3 ==========================
5 Mailing lists can be configured via the REST API.
7     >>> from mailman.app.lifecycle import create_list
8     >>> mlist = create_list('ant@example.com')
9     >>> from mailman.config import config
10     >>> transaction = config.db    
11     >>> transaction.commit()
14 Reading a configuration
15 =======================
17 All readable attributes for a list are available on a sub-resource.
19     >>> from mailman.testing.documentation import dump_json
20     >>> dump_json('http://localhost:9001/3.0/lists/ant@example.com/config')
21     accept_these_nonmembers: []
22     acceptable_aliases: []
23     admin_immed_notify: True
24     admin_notify_mchanges: False
25     administrivia: True
26     advertised: True
27     allow_list_posts: True
28     anonymous_list: False
29     archive_policy: public
30     archive_rendering_mode: text
31     autorespond_owner: none
32     autorespond_postings: none
33     autorespond_requests: none
34     autoresponse_grace_period: 90d
35     autoresponse_owner_text:
36     autoresponse_postings_text:
37     autoresponse_request_text:
38     bounce_info_stale_after: 7d
39     bounce_notify_owner_on_bounce_increment: False
40     bounce_notify_owner_on_disable: True
41     bounce_notify_owner_on_removal: True
42     bounce_score_threshold: 5
43     bounce_you_are_disabled_warnings: 3
44     bounce_you_are_disabled_warnings_interval: 7d
45     bounces_address: ant-bounces@example.com
46     collapse_alternatives: True
47     convert_html_to_plaintext: False
48     created_at: 20...T...
49     default_member_action: defer
50     default_nonmember_action: hold
51     description:
52     digest_footer_uri:
53     digest_header_uri:
54     digest_last_sent_at: None
55     digest_send_periodic: True
56     digest_size_threshold: 30.0
57     digest_volume_frequency: monthly
58     digests_enabled: True
59     discard_these_nonmembers: []
60     display_name: Ant
61     dmarc_addresses: []
62     dmarc_mitigate_action: no_mitigation
63     dmarc_mitigate_unconditionally: False
64     dmarc_moderation_notice:
65     dmarc_wrapped_message_text:
66     emergency: False
67     filter_action: discard
68     filter_content: False
69     filter_extensions: []
70     filter_types: []
71     first_strip_reply_to: False
72     footer_uri:
73     forward_unrecognized_bounces_to: administrators
74     fqdn_listname: ant@example.com
75     gateway_to_mail: False
76     gateway_to_news: False
77     goodbye_message_uri:
78     header_uri:
79     hold_these_nonmembers: []
80     http_etag: "..."
81     include_rfc2369_headers: True
82     info:
83     join_address: ant-join@example.com
84     last_post_at: None
85     leave_address: ant-leave@example.com
86     linked_newsgroup:
87     list_name: ant
88     mail_host: example.com
89     max_days_to_hold: 0
90     max_message_size: 40
91     max_num_recipients: 10
92     member_roster_visibility: moderators
93     moderator_password: None
94     newsgroup_moderation: none
95     next_digest_number: 1
96     nntp_prefix_subject_too: True
97     no_reply_address: noreply@example.com
98     owner_address: ant-owner@example.com
99     pass_extensions: []
100     pass_types: []
101     personalize: none
102     post_id: 1
103     posting_address: ant@example.com
104     posting_pipeline: default-posting-pipeline
105     preferred_language: en
106     process_bounces: True
107     reject_these_nonmembers: []
108     reply_goes_to_list: no_munging
109     reply_to_address:
110     request_address: ant-request@example.com
111     require_explicit_destination: True
112     respond_to_post_requests: True
113     send_goodbye_message: True
114     send_welcome_message: True
115     subject_prefix: [Ant]
116     subscription_policy: confirm
117     unsubscription_policy: confirm
118     usenet_watermark: None
119     volume: 1
120     welcome_message_uri:
123 Changing the full configuration
124 ===============================
126 Not all of the readable attributes can be set through the web interface.  The
127 ones that can, can either be set via ``PUT`` or ``PATCH``.  ``PUT`` changes
128 all the writable attributes in one request.
130 When using ``PUT``, all writable attributes must be included.
132     >>> dump_json('http://localhost:9001/3.0/lists/'
133     ...           'ant@example.com/config',
134     ...           dict(
135     ...             acceptable_aliases=['one@example.com', 'two@example.com'],
136     ...             accept_these_nonmembers=['aperson@example.com'],
137     ...             admin_immed_notify=False,
138     ...             admin_notify_mchanges=True,
139     ...             administrivia=False,
140     ...             advertised=False,
141     ...             anonymous_list=True,
142     ...             archive_policy='never',
143     ...             archive_rendering_mode='text',
144     ...             autorespond_owner='respond_and_discard',
145     ...             autorespond_postings='respond_and_continue',
146     ...             autorespond_requests='respond_and_discard',
147     ...             autoresponse_grace_period='45d',
148     ...             autoresponse_owner_text='the owner',
149     ...             autoresponse_postings_text='the mailing list',
150     ...             autoresponse_request_text='the robot',
151     ...             bounce_info_stale_after='5d',
152     ...             bounce_notify_owner_on_bounce_increment=False,
153     ...             bounce_notify_owner_on_disable=True,
154     ...             bounce_notify_owner_on_removal=True,
155     ...             bounce_score_threshold=5,
156     ...             bounce_you_are_disabled_warnings=3,
157     ...             bounce_you_are_disabled_warnings_interval='1d',
158     ...             forward_unrecognized_bounces_to='administrators',
159     ...             filter_extensions=['.mkv'],
160     ...             filter_types=['application/zip'],
161     ...             process_bounces=True,
162     ...             discard_these_nonmembers=[r'^name_*bperson*@example.com'],
163     ...             display_name='Fnords',
164     ...             description='This is my mailing list',
165     ...             include_rfc2369_headers=False,
166     ...             info='This is the mailing list information',
167     ...             allow_list_posts=False,
168     ...             digest_send_periodic=False,
169     ...             digest_size_threshold=10.5,
170     ...             digest_volume_frequency='yearly',
171     ...             digests_enabled=False,
172     ...             dmarc_addresses=['^.*@example.com'],
173     ...             dmarc_mitigate_action='munge_from',
174     ...             dmarc_mitigate_unconditionally=False,
175     ...             dmarc_moderation_notice='Some moderation notice',
176     ...             dmarc_wrapped_message_text='some message text',
177     ...             personalize='none',
178     ...             preferred_language='ja',
179     ...             posting_pipeline='virgin',
180     ...             filter_content=True,
181     ...             first_strip_reply_to=True,
182     ...             gateway_to_mail=True,
183     ...             gateway_to_news=True,
184     ...             linked_newsgroup='my.group',
185     ...             newsgroup_moderation='moderated',
186     ...             nntp_prefix_subject_too=False,
187     ...             convert_html_to_plaintext=True,
188     ...             collapse_alternatives=False,
189     ...             reject_these_nonmembers=[r'^b[hello]*@example.com'],
190     ...             hold_these_nonmembers=[r'^re[gG]ex@example.com'],
191     ...             reply_goes_to_list='point_to_list',
192     ...             reply_to_address='bee@example.com',
193     ...             require_explicit_destination=False,
194     ...             member_roster_visibility='members',
195     ...             send_goodbye_message=False,
196     ...             send_welcome_message=False,
197     ...             subject_prefix='[ant]',
198     ...             subscription_policy='moderate',
199     ...             unsubscription_policy='confirm',
200     ...             default_member_action='hold',
201     ...             default_nonmember_action='discard',
202     ...             moderator_password='password',
203     ...             max_message_size='500',
204     ...             respond_to_post_requests=True,
205     ...             max_days_to_hold='20',
206     ...             max_num_recipients='20',
207     ...             pass_extensions=['.pdf'],
208     ...             pass_types=['image/jpeg'],
209     ...             filter_action='preserve',
210     ...             emergency=False,
211     ...             ),
212     ...           'PUT')
213     date: ...
214     server: ...
215     status: 204
217 These values are changed permanently.
219     >>> dump_json('http://localhost:9001/3.0/lists/'
220     ...           'ant@example.com/config')
221     accept_these_nonmembers: ['aperson@example.com']
222     acceptable_aliases: ['one@example.com', 'two@example.com']
223     admin_immed_notify: False
224     admin_notify_mchanges: True
225     administrivia: False
226     advertised: False
227     allow_list_posts: False
228     anonymous_list: True
229     archive_policy: never
230     archive_rendering_mode: text
231     autorespond_owner: respond_and_discard
232     autorespond_postings: respond_and_continue
233     autorespond_requests: respond_and_discard
234     autoresponse_grace_period: 45d
235     autoresponse_owner_text: the owner
236     autoresponse_postings_text: the mailing list
237     autoresponse_request_text: the robot
238     bounce_info_stale_after: 5d
239     bounce_notify_owner_on_bounce_increment: False
240     bounce_notify_owner_on_disable: True
241     bounce_notify_owner_on_removal: True
242     bounce_score_threshold: 5
243     bounce_you_are_disabled_warnings: 3
244     bounce_you_are_disabled_warnings_interval: 1d
245     ...
246     collapse_alternatives: False
247     convert_html_to_plaintext: True
248     ...
249     default_member_action: hold
250     default_nonmember_action: discard
251     description: This is my mailing list
252     ...
253     digest_send_periodic: False
254     digest_size_threshold: 10.5
255     digest_volume_frequency: yearly
256     digests_enabled: False
257     discard_these_nonmembers: ['^name_*bperson*@example.com']
258     display_name: Fnords
259     dmarc_addresses: ['^.*@example.com']
260     dmarc_mitigate_action: munge_from
261     dmarc_mitigate_unconditionally: False
262     dmarc_moderation_notice: Some moderation notice
263     dmarc_wrapped_message_text: some message text
264     emergency: False
265     filter_action: preserve
266     filter_content: True
267     filter_extensions: ['.mkv']
268     filter_types: ['application/zip']
269     first_strip_reply_to: True
270     footer_uri:
271     forward_unrecognized_bounces_to: administrators
272     fqdn_listname: ant@example.com
273     gateway_to_mail: True
274     gateway_to_news: True
275     ...
276     hold_these_nonmembers: ['^re[gG]ex@example.com']
277     http_etag: "..."
278     include_rfc2369_headers: False
279     ...
280     member_roster_visibility: members
281     moderator_password: {plaintext}password
282     newsgroup_moderation: moderated
283     ...
284     nntp_prefix_subject_too: False
285     ...
286     pass_extensions: ['.pdf']
287     pass_types: ['image/jpeg']
288     ...
289     posting_pipeline: virgin
290     preferred_language: ja
291     process_bounces: True
292     reject_these_nonmembers: ['^b[hello]*@example.com']
293     reply_goes_to_list: point_to_list
294     reply_to_address: bee@example.com
295     ...
296     require_explicit_destination: False
297     respond_to_post_requests: True
298     send_goodbye_message: False
299     send_welcome_message: False
300     subject_prefix: [ant]
301     subscription_policy: moderate
302     unsubscription_policy: confirm
303     ...
306 Changing a partial configuration
307 ================================
309 Using ``PATCH``, you can change just one attribute.
311     >>> dump_json('http://localhost:9001/3.0/lists/'
312     ...           'ant@example.com/config',
313     ...           dict(display_name='My List'),
314     ...           'PATCH')
315     date: ...
316     server: ...
317     status: 204
319 These values are changed permanently.
321     >>> print(mlist.display_name)
322     My List
325 Sub-resources
326 =============
328 Mailing list configuration variables are actually available as sub-resources
329 on the mailing list.  Their values can be retrieved and set through the
330 sub-resource.
333 Simple resources
334 ----------------
336 You can view the current value of the sub-resource.
338     >>> dump_json('http://localhost:9001/3.0/lists/ant.example.com'
339     ...           '/config/display_name')
340     display_name: My List
341     http_etag: ...
343 The resource can be changed by PUTting to it.  Note that the value still
344 requires a dictionary, and that dictionary must have a single key matching the
345 name of the resource.
348     >>> dump_json('http://localhost:9001/3.0/lists/ant.example.com'
349     ...           '/config/display_name',
350     ...           dict(display_name='Your List'),
351     ...           'PUT')
352     date: ...
353     server: ...
354     status: 204
356     >>> dump_json('http://localhost:9001/3.0/lists/ant.example.com'
357     ...           '/config/display_name')
358     display_name: Your List
359     http_etag: ...
361 PATCH works the same way, with the same effect, so you can choose to use
362 either method.
364     >>> dump_json('http://localhost:9001/3.0/lists/ant.example.com'
365     ...           '/config/display_name',
366     ...           dict(display_name='Their List'),
367     ...           'PATCH')
368     date: ...
369     server: ...
370     status: 204
372     >>> dump_json('http://localhost:9001/3.0/lists/ant.example.com'
373     ...           '/config/display_name')
374     display_name: Their List
375     http_etag: ...
378 Acceptable aliases
379 ------------------
381 These are recipient aliases that can be used in the ``To:`` and ``CC:``
382 headers instead of the posting address.  They can also be strings beginning
383 with ``^`` which are interpreted as regular expressions matched against
384 addresses in ``To:`` and ``CC:`` headers.  They are often used in forwarded
385 emails.  By default, a mailing list has no acceptable aliases.
387     >>> from mailman.interfaces.mailinglist import IAcceptableAliasSet
388     >>> IAcceptableAliasSet(mlist).clear()
389     >>> transaction.commit()
390     >>> dump_json('http://localhost:9001/3.0/lists/'
391     ...           'ant@example.com/config/acceptable_aliases')
392     acceptable_aliases: []
393     http_etag: "..."
395 We can add a few by ``PUT``-ing them on the sub-resource.  The keys in the
396 dictionary are ignored.
398     >>> dump_json('http://localhost:9001/3.0/lists/'
399     ...           'ant@example.com/config/acceptable_aliases',
400     ...           dict(acceptable_aliases=['foo@example.com',
401     ...                                    'bar@example.net']),
402     ...           'PUT')
403     date: ...
404     server: ...
405     status: 204
407 You can get all the mailing list's acceptable aliases through the REST API.
409     >>> from mailman.testing.documentation import call_http
410     >>> response = call_http(
411     ...     'http://localhost:9001/3.0/lists/'
412     ...     'ant@example.com/config/acceptable_aliases')
413     >>> for alias in response['acceptable_aliases']:
414     ...     print(alias)
415     bar@example.net
416     foo@example.com
418 The mailing list has its aliases set.
420     >>> from mailman.interfaces.mailinglist import IAcceptableAliasSet
421     >>> aliases = IAcceptableAliasSet(mlist)
422     >>> for alias in sorted(aliases.aliases):
423     ...     print(alias)
424     bar@example.net
425     foo@example.com
427 The aliases can be removed by using ``DELETE``.
429     >>> response = call_http(
430     ...     'http://localhost:9001/3.0/lists/'
431     ...     'ant@example.com/config/acceptable_aliases',
432     ...     method='DELETE')
433     date: ...
434     server: ...
435     status: 204
437 Now the mailing list has no aliases.
439     >>> aliases = IAcceptableAliasSet(mlist)
440     >>> print(len(list(aliases.aliases)))
441     0
444 Header matches
445 --------------
447 Mailman can do pattern based header matching during its normal rule
448 processing.  Each mailing list can also be configured with a set of header
449 matching regular expression rules.  These can be used to impose list-specific
450 header filtering with the same semantics as the global ``[antispam]`` section,
451 or to have a different action.
453 The list of header matches for a mailing list are returned on the
454 ``header-matches`` child of this list.
456     >>> dump_json('http://localhost:9001/3.0/lists/ant.example.com'
457     ...           '/header-matches')
458     http_etag: "..."
459     start: 0
460     total_size: 0
462 New header matches can be created by POSTing to the resource.
465     >>> dump_json('http://localhost:9001/3.0/lists/ant.example.com'
466     ...           '/header-matches', {
467     ...           'header': 'X-Spam-Flag',
468     ...           'pattern': '^Yes',
469     ...           })
470     content-length: 0
471     ...
472     location: .../3.0/lists/ant.example.com/header-matches/0
473     ...
474     status: 201
476     >>> dump_json('http://localhost:9001/3.0/lists/ant.example.com'
477     ...           '/header-matches/0')
478     header: x-spam-flag
479     http_etag: "..."
480     pattern: ^Yes
481     position: 0
482     self_link: http://localhost:9001/3.0/lists/ant.example.com/header-matches/0
484 To follow the global antispam action, the header match rule must not specify
485 an ``action`` key, which names the chain to jump to if the rule matches.  If
486 the default antispam action is changed in the configuration file and Mailman
487 is restarted, those rules will get the new jump action.  If a specific action
488 is desired, the ``action`` key must name a valid chain to jump to.
491     >>> dump_json('http://localhost:9001/3.0/lists/ant.example.com'
492     ...           '/header-matches', {
493     ...           'header': 'X-Spam-Status',
494     ...           'pattern': '^Yes',
495     ...           'action': 'discard',
496     ...           })
497     content-length: 0
498     ...
499     location: .../3.0/lists/ant.example.com/header-matches/1
500     ...
501     status: 201
503     >>> dump_json('http://localhost:9001/3.0/lists/ant.example.com'
504     ...           '/header-matches/1')
505     action: discard
506     header: x-spam-status
507     http_etag: "..."
508     pattern: ^Yes
509     position: 1
510     self_link: http://localhost:9001/3.0/lists/ant.example.com/header-matches/1
512 The resource can be changed by PATCHing it.  The ``position`` key can be used
513 to change the priority of the header match in the list.  If it is not supplied,
514 the priority is not changed.
517     >>> dump_json('http://localhost:9001/3.0/lists/ant.example.com'
518     ...           '/header-matches/1',
519     ...           dict(pattern='^No', action='accept'),
520     ...           'PATCH')
521     date: ...
522     server: ...
523     status: 204
524     >>> dump_json('http://localhost:9001/3.0/lists/ant.example.com'
525     ...           '/header-matches/1')
526     action: accept
527     header: x-spam-status
528     http_etag: "..."
529     pattern: ^No
530     position: 1
531     self_link: http://localhost:9001/3.0/lists/ant.example.com/header-matches/1
533     >>> dump_json('http://localhost:9001/3.0/lists/ant.example.com'
534     ...           '/header-matches/1',
535     ...           dict(position=0),
536     ...           'PATCH')
537     date: ...
538     server: ...
539     status: 204
540     >>> dump_json('http://localhost:9001/3.0/lists/ant.example.com'
541     ...           '/header-matches')
542     entry 0:
543         action: accept
544         header: x-spam-status
545         http_etag: "..."
546         pattern: ^No
547         position: 0
548         self_link: .../lists/ant.example.com/header-matches/0
549     entry 1:
550         header: x-spam-flag
551         http_etag: "..."
552         pattern: ^Yes
553         position: 1
554         self_link: .../lists/ant.example.com/header-matches/1
555     http_etag: "..."
556     start: 0
557     total_size: 2
559 The PUT method can replace an entire header match.  The ``position`` key is
560 optional; if it is omitted, the order will not be changed.
563     >>> dump_json('http://localhost:9001/3.0/lists/ant.example.com'
564     ...           '/header-matches/1',
565     ...           dict(header='X-Spam-Status',
566     ...                pattern='^Yes',
567     ...                action='hold',
568     ...           ), 'PUT')
569     date: ...
570     server: ...
571     status: 204
573     >>> dump_json('http://localhost:9001/3.0/lists/ant.example.com'
574     ...           '/header-matches/1')
575     action: hold
576     header: x-spam-status
577     http_etag: "..."
578     pattern: ^Yes
579     position: 1
580     self_link: http://localhost:9001/3.0/lists/ant.example.com/header-matches/1
582 A header match can be removed using the DELETE method.
585     >>> dump_json('http://localhost:9001/3.0/lists/ant.example.com'
586     ...           '/header-matches/1',
587     ...           method='DELETE')
588     date: ...
589     server: ...
590     status: 204
592     >>> dump_json('http://localhost:9001/3.0/lists/ant.example.com'
593     ...           '/header-matches')
594     entry 0:
595         action: accept
596         header: x-spam-status
597         http_etag: "..."
598         pattern: ^No
599         position: 0
600         self_link: .../lists/ant.example.com/header-matches/0
601     http_etag: "..."
602     start: 0
603     total_size: 1
605 The mailing list's header matches can be cleared by issuing a DELETE request on
606 the top resource.
609     >>> dump_json('http://localhost:9001/3.0/lists/ant.example.com'
610     ...           '/header-matches',
611     ...           method='DELETE')
612     date: ...
613     server: ...
614     status: 204
616     >>> dump_json('http://localhost:9001/3.0/lists/ant.example.com'
617     ...           '/header-matches')
618     http_etag: "..."
619     start: 0
620     total_size: 0