mailcow-dockerized icon indicating copy to clipboard operation
mailcow-dockerized copied to clipboard

Aliases for all domains

Open floli opened this issue 2 years ago • 1 comments

Summary

Allow to create an alias for all domains, currently existing ones as well as newly created domains.

Motivation

Use case would mainly be for RFC required aliases, such as postmaster (or hostmaster, webmaster, ...) which is usually the same person for all domains.

Creating the aliases for every new domain is tedious and likely to be forgotten, resulting in an unreachable postmaster address.

Additional context

As we can create a catchall-all-users alias using @example.org, a complementary syntax would be to use postmaster@ as a catch-all-domains alias.

floli avatar Mar 13 '22 11:03 floli

I hacked together a little python script that creates aliases for all domains:

from urllib.request import Request, urlopen
import json, re, itertools
import logging

class MailcowAPI:
    def __init__(self, domain: str, key: str):
        self.domain = domain
        self.key = key

    def rest_call(self, path: str, body = None):
        url = "https://" + self.domain + "/api/v1/" + path
        headers = {"accept" : "application/json",
                   "X-API-Key" : self.key,
                   'content-type' : 'application/json'}
        data = json.dumps(body).encode("utf-8") if body else None
        request = Request(url, data, headers)
        logging.debug("Opening URL: %s with body: %s", url, body)
        with urlopen(request) as response:
            content = response.read().decode("utf-8")

            try:
                json_content = json.loads(content)
                message_type = json_content["type"]
                message_content = json_content["msg"]
            except (json.JSONDecodeError, AttributeError, TypeError):
                pass
            else:
                if message_type == "error":
                    raise Exception(f"Error calling URL {url} with data {data} returned with message: {message_content}.")
                


        return json.loads(content)

    def mailboxes(self):
        return self.rest_call(path = "get/mailbox/all")

    def logs(self, log_type: str, count: int = 10):
        return self.rest_call("get/logs/" + log_type + "/" + str(count))

    def aliases(self, id = "all"):
        return self.rest_call("get/alias/" + str(id))

    def add_alias(self, address, goto):
        self.rest_call("add/alias", {"address" : address, "goto" : goto, "active" : True})
        logging.debug("Created alias: %s -> %s", address, goto)

    def delete_alias(self, id: int):
        self.rest_call("delete/alias", body = [str(id)])
        logging.debug("Deleted alias %s", id)

    def domains(self, id = "all"):
        return self.rest_call("get/domain/" + str(id))


def set_aliases_for_every_domain(mc: MailcowAPI):
    target = "[email protected]"
    aliases = ["postmaster", "hostmaster"]
    domain_regexp = r".*"
    force = False  # delete existing aliases and recreate

    domains = [ i["domain_name"] for i in mc.domains() if re.match(domain_regexp, i["domain_name"]) ]
    existing_aliases = { i["address"]: i["id"] for i in mc.aliases() }
    aliases_to_create = [ a+"@"+d for a in aliases for d in domains ]

    for a in aliases_to_create:
        if a in existing_aliases and force is False:
            logging.info("Aliases for %s already exists, skipping.", a)
        elif a in existing_aliases and force is True:
            logging.info("Aliases for %s already exists, deleting and set again.", a)
            mc.delete_alias(existing_aliases[a])
            mc.add_alias(a, target)
        else:
            mc.add_alias(a, target)


def main():
    logging.basicConfig(level = logging.DEBUG)
    key = "An API key with write permissions"
    domain = "the.mail.domain"

    mc = MailcowAPI(domain, key)
    set_aliases_for_every_domain(mc)

main()

Fell free to use, modify and distribute (public domain).

floli avatar Mar 16 '22 20:03 floli