Domains

Domains are how Mailman interacts with email host names and web host names.

# The test framework starts out with an example domain, so let's delete
# that first.
>>> from mailman.interfaces.domain import IDomainManager
>>> from zope.component import getUtility
>>> domain_manager = getUtility(IDomainManager)

>>> domain_manager.remove('example.com')
<Domain example.com...>
>>> from mailman.config import config
>>> transaction = config.db
>>> transaction.commit()

The REST API can be queried for the set of known domains, of which there are initially none.

>>> from mailman.testing.documentation import dump_json
>>> dump_json('http://localhost:9001/3.0/domains')
http_etag: "..."
start: 0
total_size: 0

Once a domain is added, it is accessible through the API.

>>> domain_manager.add('example.com', 'An example domain')
<Domain example.com, An example domain>
>>> transaction.commit()

>>> dump_json('http://localhost:9001/3.0/domains')
entry 0:
    alias_domain: None
    base_url: None
    description: An example domain
    http_etag: "..."
    mail_host: example.com
    self_link: http://localhost:9001/3.0/domains/example.com
http_etag: "..."
start: 0
total_size: 1

At the top level, all domains are returned as separate entries.

>>> domain_manager.add('example.org',)
<Domain example.org>
>>> domain_manager.add(
...     'lists.example.net',
...     'Porkmasters')
<Domain lists.example.net, Porkmasters>
>>> # A list with an alias domain.
>>> domain_manager.add(
...     'mm.example.net',
...     'Porkmasters2',
...     alias_domain='mmx.example.net')
<Domain mm.example.net, Porkmasters2, alias: mmx.example.net>
>>> transaction.commit()

>>> dump_json('http://localhost:9001/3.0/domains')
entry 0:
    alias_domain: None
    base_url: None
    description: An example domain
    http_etag: "..."
    mail_host: example.com
    self_link: http://localhost:9001/3.0/domains/example.com
entry 1:
    alias_domain: None
    base_url: None
    description: None
    http_etag: "..."
    mail_host: example.org
    self_link: http://localhost:9001/3.0/domains/example.org
entry 2:
    alias_domain: None
    base_url: None
    description: Porkmasters
    http_etag: "..."
    mail_host: lists.example.net
    self_link: http://localhost:9001/3.0/domains/lists.example.net
entry 3:
    alias_domain: mmx.example.net
    base_url: None
    description: Porkmasters2
    http_etag: "..."
    mail_host: mm.example.net
    self_link: http://localhost:9001/3.0/domains/mm.example.net
http_etag: "..."
start: 0
total_size: 4

Individual domains

The information for a single domain is available by following one of the self_links from the above collection.

>>> dump_json('http://localhost:9001/3.0/domains/lists.example.net')
alias_domain: None
base_url: None
description: Porkmasters
http_etag: "..."
mail_host: lists.example.net
self_link: http://localhost:9001/3.0/domains/lists.example.net

You can also list all the mailing lists for a given domain. At first, the example.com domain does not contain any mailing lists.

>>> dump_json('http://localhost:9001/3.0/domains/example.com/lists')
http_etag: "..."
start: 0
total_size: 0

>>> dump_json('http://localhost:9001/3.0/lists', {
...           'fqdn_listname': 'test-domains@example.com',
...           })
content-length: 0
content-type: application/json
date: ...
location: http://localhost:9001/3.0/lists/test-domains.example.com
...

>>> dump_json('http://localhost:9001/3.0/domains/example.com/lists')
entry 0:
    advertised: True
    description:
    display_name: Test-domains
    fqdn_listname: test-domains@example.com
    http_etag: "..."
    ...
    member_count: 0
    self_link: http://localhost:9001/3.0/lists/test-domains.example.com
    volume: 1
http_etag: "..."
start: 0
total_size: 1

Other domains continue to contain no mailing lists.

>>> dump_json('http://localhost:9001/3.0/domains/lists.example.net/lists')
http_etag: "..."
start: 0
total_size: 0

Creating new domains

New domains can be created by posting to the domains url.

>>> dump_json('http://localhost:9001/3.0/domains', {
...           'mail_host': 'lists.example.com',
...           })
content-length: 0
content-type: application/json
date: ...
location: http://localhost:9001/3.0/domains/lists.example.com
...

Now the web service knows about our new domain.

>>> dump_json('http://localhost:9001/3.0/domains/lists.example.com')
alias_domain: None
base_url: None
description: None
http_etag: "..."
mail_host: lists.example.com
self_link: http://localhost:9001/3.0/domains/lists.example.com

And the new domain is in our database.

>>> domain_manager['lists.example.com']
<Domain lists.example.com>

# Unlock the database.
>>> transaction.abort()

You can also create a new domain with a description and a contact address.

>>> dump_json('http://localhost:9001/3.0/domains', {
...           'mail_host': 'my.example.com',
...           'description': 'My new domain',
...           })
content-length: 0
content-type: application/json
date: ...
location: http://localhost:9001/3.0/domains/my.example.com
...

>>> dump_json('http://localhost:9001/3.0/domains/my.example.com')
alias_domain: None
base_url: None
description: My new domain
http_etag: "..."
mail_host: my.example.com
self_link: http://localhost:9001/3.0/domains/my.example.com

>>> domain_manager['my.example.com']
<Domain my.example.com, My new domain>

# Unlock the database.
>>> transaction.abort()

Domain URLs

Domain have a base_url property which allows Mailman to generate URLs for Mailman’s Web Inteface. URL patterns are configured under [urlpatterns] configuration.

Initially, the domain doesn’t have base_url:

>>> dump_json('http://localhost:9001/3.0/domains/my.example.com')
alias_domain: None
base_url: None
description: My new domain
http_etag: "..."
mail_host: my.example.com
self_link: http://localhost:9001/3.0/domains/my.example.com

Set the URL to the base_url of the web interface. If you are using Postorius it will look something like https://lists.example.com/mailman3 or https://lists.example.com/postorius:

>>> dump_json('http://localhost:9001/3.0/domains/my.example.com', {
...           'base_url': 'https://lists.example.com/mailman3',
...           }, method='PATCH')
date: ...
server: gunicorn
status: 204

>>> dump_json('http://localhost:9001/3.0/domains/my.example.com')
alias_domain: None
base_url: https://lists.example.com/mailman3
description: My new domain
http_etag: "..."
mail_host: my.example.com
self_link: http://localhost:9001/3.0/domains/my.example.com

Deleting domains

Domains can also be deleted via the API.

>>> dump_json('http://localhost:9001/3.0/domains/lists.example.com',
...           method='DELETE')
date: ...
server: ...
status: 204

Domain owners

Domains can have owners. By posting some addresses to the owners resource, you can add some domain owners. Currently our domain has no owners:

>>> dump_json('http://localhost:9001/3.0/domains/my.example.com/owners')
http_etag: ...
start: 0
total_size: 0

Anne and Bart volunteer to be a domain owners.

>>> dump_json('http://localhost:9001/3.0/domains/my.example.com/owners', (
...     ('owner', 'anne@example.com'), ('owner', 'bart@example.com')
...     ))
date: ...
server: ...
status: 204

>>> dump_json('http://localhost:9001/3.0/domains/my.example.com/owners',
...     sort_entries='user_id')
entry 0:
    created_on: 2005-08-01T07:49:23
    http_etag: ...
    is_server_owner: False
    self_link: http://localhost:9001/3.0/users/1
    user_id: 1
entry 1:
    created_on: 2005-08-01T07:49:23
    http_etag: ...
    is_server_owner: False
    self_link: http://localhost:9001/3.0/users/2
    user_id: 2
http_etag: ...
start: 0
total_size: 2

We can delete all the domain owners.

>>> dump_json('http://localhost:9001/3.0/domains/my.example.com/owners',
...           method='DELETE')
date: ...
server: ...
status: 204

Now there are no owners.

>>> dump_json('http://localhost:9001/3.0/domains/my.example.com/owners')
http_etag: ...
start: 0
total_size: 0

New domains can be created with owners.

>>> dump_json('http://localhost:9001/3.0/domains', (
...           ('mail_host', 'your.example.com'),
...           ('owner', 'anne@example.com'),
...           ('owner', 'bart@example.com'),
...           ))
content-length: 0
content-type: application/json
date: ...
location: http://localhost:9001/3.0/domains/your.example.com
server: ...
status: 201

The new domain has the expected owners.

>>> dump_json('http://localhost:9001/3.0/domains/your.example.com/owners',
...     sort_entries='user_id')
entry 0:
    created_on: 2005-08-01T07:49:23
    http_etag: ...
    is_server_owner: False
    self_link: http://localhost:9001/3.0/users/1
    user_id: 1
entry 1:
    created_on: 2005-08-01T07:49:23
    http_etag: ...
    is_server_owner: False
    self_link: http://localhost:9001/3.0/users/2
    user_id: 2
http_etag: ...
start: 0
total_size: 2