Skip to content

Zammad API

Es werden folgende Environment Variablen benötigt:

ZAMMAD_URL=https://zammad.jafar.live/api/v1/
ZAMMAD_USERNAME=... # Standard ist die Mailadresse
ZAMMAD_PASSWORD=...

Der Zammad-User muss über die passenden Adminrechte für die gewünschten Operationen verfügen!

Die Einbettung des Api-Clients ermöglicht .create_ticket(), um auf Grundlage weniger Rahmendaten (E-Mail-Adresse, Betreff, Body und Ziel-Queue) ein Ticket zu erstellen.

Unbekannte User werden dabei neu erstellt, bekannte User im System gefunden.

Darüber hinaus stellt die API Methoden bereit, um auf Tickets zu antworten sowie aus einer gegebenen Liste von Queue-Namen deren Äquivalent als Gruppe in Zammad zu erstellen.

src.utils.zammad_api.ZammadApiClient

Ein Client zur Interaktion mit der Zammad API für Benutzer- und Ticketverwaltung.

Source code in src\utils\zammad_api.py
class ZammadApiClient:
    """
    Ein Client zur Interaktion mit der Zammad API für Benutzer- und Ticketverwaltung.
    """

    def __init__(
        self,
        url=os.getenv("ZAMMAD_URL"),
        username=os.getenv("ZAMMAD_USERNAME"),
        password=os.getenv("ZAMMAD_PASSWORD"),
    ):
        """
        Initialisiert den Zammad API Client.

        Args:
            url (str): Die URL Ihrer Zammad-Instanz.
            username (str): Zammad Username.
            password (str): Zammad Passwort.
        """
        self.client = ZammadAPI(url=url, username=username, password=password)
        print("Zammad API Client initialisiert.")

    def _handle_api_error(self, e, message="Ein Fehler ist aufgetreten"):
        """
        Behandelt API-Fehler und gibt detaillierte Informationen aus.

        Args:
            e (Exception): Die aufgetretene Ausnahme.
            message (str): Eine benutzerdefinierte Fehlermeldung.
        """
        print(f"{message}: {e}")
        if hasattr(e, "response") and e.response is not None:
            try:
                print(f"API Fehlerdetails: {e.response.json()}")
            except ValueError:
                print(f"API Fehlerdetails (Text): {e.response.text}")

    def find_or_create_customer(
        self, email, firstname_default="Imported", lastname_default="User"
    ):
        """
        Sucht einen Kunden anhand der E-Mail-Adresse. Wenn nicht gefunden, wird ein
        neuer Kunde erstellt.

        Args:
            email (str): Die E-Mail-Adresse des Kunden.
            firstname_default (str): Der Standard-Vorname für einen neuen Kunden.
            lastname_default (str): Der Standard-Nachname für einen neuen Kunden.

        Returns:
            int: Die ID des gefundenen oder neu erstellten Kunden. None: Wenn ein Fehler
            auftritt.
        """
        try:
            print(f"Suche nach Kunde mit E-Mail: {email}...")
            # Die Suche nach 'email:<email>' ist oft präziser
            search_query = f"email:{email}"
            users = self.client.user.search({search_query})

            found_user = None
            if users:
                for user in users:  # Sicherstellen, dass die E-Mail exakt übereinstimmt
                    if "email" in user and user["email"].lower() == email.lower():
                        found_user = user
                        break

            if found_user:
                customer_id = found_user["id"]
                print(f"Kunde gefunden mit ID: {customer_id}")
                return customer_id
            else:
                print(f"Kunde nicht gefunden. Erstelle neuen Kunden: {email}")
                user_params = {
                    "firstname": firstname_default,
                    "lastname": lastname_default,
                    "email": email,
                    "login": email,
                    "roles": ["Customer"],
                    "active": True,
                }
                new_customer = self.client.user.create(user_params)
                customer_id = new_customer["id"]
                print(f"Neuer Kunde erstellt mit ID: {customer_id}")
                return customer_id

        except Exception as e:
            self._handle_api_error(
                e, f"Fehler beim Suchen oder Erstellen des Kunden ({email})"
            )
            return None

    def create_ticket(
        self,
        customer_email,
        subject,
        queue,
        body,
        priority_id=2,
        type_id_incident=2,
    ):
        """
        Erstellt ein neues Ticket in Zammad.

        Args:
            customer_id (int): Die ID des Kunden, für den das Ticket erstellt wird.
            subject (str): Der Betreff des Tickets.
            queue (str): Der Name der Ticket-Warteschlange (Gruppe).
            body (str): Der Hauptinhalt des Tickets (erste Artikel).
            priority_id (int): Die ID der Priorität des Tickets.
            type_id_incident (int): Die ID des Ticket-Typs (z.B. Incident).

        Returns:
            dict: Das erstellte Ticket-Objekt, oder None bei einem Fehler.
        """

        customer_id = self.find_or_create_customer(customer_email)

        try:
            print("Versuche, Ticket zu erstellen...")
            ticket_params = {
                "title": subject,
                "group": queue,
                "customer_id": customer_id,
                "priority_id": priority_id,
                "type_id": type_id_incident,
                "article": {
                    "body": body,
                    # Typ wird auf "web" gesetzt, weil keine Mailadresse an mein System
                    # angebunden ist (eigentlich bedeutet "web", dass Leute ihr Ticket
                    # über die Zammad-Website erstellt haben)
                    "type": "web",
                    "internal": False,
                    "sender": "Customer",
                },
            }

            new_ticket = self.client.ticket.create(ticket_params)
            print(f"Ticket erfolgreich erstellt mit ID: {new_ticket['id']}.")
            return new_ticket
        except Exception as e:
            self._handle_api_error(
                e, "Ein Fehler ist aufgetreten beim Erstellen des Tickets"
            )
            return None

    #### Hier noch eine optionale Funktion, um eine automatische Antwort zu erstellen

    def add_ticket_article(self, ticket_id, customer_id, agent_sender_id, body_answer):
        """Fügt einem bestehenden Ticket einen neuen Artikel (z.B. eine
        Antwort) hinzu.

        Args:

            ticket_id (int): Die ID des Tickets, zu dem der Artikel hinzugefügt

            customer_id (int): Die ID des Kunden.

            agent_sender_id (int): Die ID des sendenden Agenten (typischerweise 1 für
                Zammad System/Default Agent).

            body_answer (str): Der Inhalt des Artikels.

        Returns:

            dict: Der erstellte Artikel, oder None bei einem Fehler.
        """
        try:
            print("Versuche, Antwort als Artikel hinzuzufügen...")
            article_params = {
                "ticket_id": ticket_id,
                "customer_id": customer_id,
                "sender_id": agent_sender_id,
                "body": body_answer,
                "type": "web",
                "internal": False,
                "sender": "Agent",
            }

            new_article = self.client.ticket_article.create(article_params)
            print(
                f"""Antwort-Artikel erfolgreich zum Ticket {ticket_id} hinzugefügt mit
                 ID: {new_article["id"]}"""
            )
            return new_article
        except Exception as e:
            self._handle_api_error(
                e, "Ein Fehler ist aufgetreten beim Hinzufügen des Artikels"
            )
            return None

    def create_groups(self, group_names: List[String]):
        """
        Erstellt aus eine Liste neue Gruppen in Zammad.

        Args:
            group_names (list): Eine Liste von Strings, wobei jeder String der Name
                einer zu erstellenden Gruppe ist.

        Returns:
            list: Eine Liste von Dictionaries, die die Details der erfolgreich
                erstellten Gruppen enthalten. Jedes Dictionary enthält den Gruppennamen
                und den API-Response für diese Gruppe, oder eine Fehlermeldung, wenn die
                Erstellung fehlschlägt.
        """
        created_groups_info = []
        for group_name in group_names:
            try:
                print(f"Versuche, Gruppe '{group_name}' zu erstellen...")
                group_data = {"name": group_name}
                response = self.client.group.create(group_data)
                created_groups_info.append(
                    {
                        "group_name": group_name,
                        "status": "success",
                        "response": response,
                    }
                )
                print(f"Gruppe '{group_name}' erfolgreich erstellt.")
            except Exception as e:
                self._handle_api_error(
                    e, message=f"Fehler beim Erstellen der Gruppe '{group_name}'"
                )
                created_groups_info.append(
                    {"group_name": group_name, "status": "failed", "error": str(e)}
                )
        return created_groups_info

__init__(url=os.getenv('ZAMMAD_URL'), username=os.getenv('ZAMMAD_USERNAME'), password=os.getenv('ZAMMAD_PASSWORD'))

Initialisiert den Zammad API Client.

Parameters:

Name Type Description Default
url str

Die URL Ihrer Zammad-Instanz.

getenv('ZAMMAD_URL')
username str

Zammad Username.

getenv('ZAMMAD_USERNAME')
password str

Zammad Passwort.

getenv('ZAMMAD_PASSWORD')
Source code in src\utils\zammad_api.py
def __init__(
    self,
    url=os.getenv("ZAMMAD_URL"),
    username=os.getenv("ZAMMAD_USERNAME"),
    password=os.getenv("ZAMMAD_PASSWORD"),
):
    """
    Initialisiert den Zammad API Client.

    Args:
        url (str): Die URL Ihrer Zammad-Instanz.
        username (str): Zammad Username.
        password (str): Zammad Passwort.
    """
    self.client = ZammadAPI(url=url, username=username, password=password)
    print("Zammad API Client initialisiert.")

add_ticket_article(ticket_id, customer_id, agent_sender_id, body_answer)

Fügt einem bestehenden Ticket einen neuen Artikel (z.B. eine Antwort) hinzu.

Args:

ticket_id (int): Die ID des Tickets, zu dem der Artikel hinzugefügt

customer_id (int): Die ID des Kunden.

agent_sender_id (int): Die ID des sendenden Agenten (typischerweise 1 für
    Zammad System/Default Agent).

body_answer (str): Der Inhalt des Artikels.

Returns:

dict: Der erstellte Artikel, oder None bei einem Fehler.
Source code in src\utils\zammad_api.py
def add_ticket_article(self, ticket_id, customer_id, agent_sender_id, body_answer):
    """Fügt einem bestehenden Ticket einen neuen Artikel (z.B. eine
    Antwort) hinzu.

    Args:

        ticket_id (int): Die ID des Tickets, zu dem der Artikel hinzugefügt

        customer_id (int): Die ID des Kunden.

        agent_sender_id (int): Die ID des sendenden Agenten (typischerweise 1 für
            Zammad System/Default Agent).

        body_answer (str): Der Inhalt des Artikels.

    Returns:

        dict: Der erstellte Artikel, oder None bei einem Fehler.
    """
    try:
        print("Versuche, Antwort als Artikel hinzuzufügen...")
        article_params = {
            "ticket_id": ticket_id,
            "customer_id": customer_id,
            "sender_id": agent_sender_id,
            "body": body_answer,
            "type": "web",
            "internal": False,
            "sender": "Agent",
        }

        new_article = self.client.ticket_article.create(article_params)
        print(
            f"""Antwort-Artikel erfolgreich zum Ticket {ticket_id} hinzugefügt mit
             ID: {new_article["id"]}"""
        )
        return new_article
    except Exception as e:
        self._handle_api_error(
            e, "Ein Fehler ist aufgetreten beim Hinzufügen des Artikels"
        )
        return None

create_groups(group_names)

Erstellt aus eine Liste neue Gruppen in Zammad.

Parameters:

Name Type Description Default
group_names list

Eine Liste von Strings, wobei jeder String der Name einer zu erstellenden Gruppe ist.

required

Returns:

Name Type Description
list

Eine Liste von Dictionaries, die die Details der erfolgreich erstellten Gruppen enthalten. Jedes Dictionary enthält den Gruppennamen und den API-Response für diese Gruppe, oder eine Fehlermeldung, wenn die Erstellung fehlschlägt.

Source code in src\utils\zammad_api.py
def create_groups(self, group_names: List[String]):
    """
    Erstellt aus eine Liste neue Gruppen in Zammad.

    Args:
        group_names (list): Eine Liste von Strings, wobei jeder String der Name
            einer zu erstellenden Gruppe ist.

    Returns:
        list: Eine Liste von Dictionaries, die die Details der erfolgreich
            erstellten Gruppen enthalten. Jedes Dictionary enthält den Gruppennamen
            und den API-Response für diese Gruppe, oder eine Fehlermeldung, wenn die
            Erstellung fehlschlägt.
    """
    created_groups_info = []
    for group_name in group_names:
        try:
            print(f"Versuche, Gruppe '{group_name}' zu erstellen...")
            group_data = {"name": group_name}
            response = self.client.group.create(group_data)
            created_groups_info.append(
                {
                    "group_name": group_name,
                    "status": "success",
                    "response": response,
                }
            )
            print(f"Gruppe '{group_name}' erfolgreich erstellt.")
        except Exception as e:
            self._handle_api_error(
                e, message=f"Fehler beim Erstellen der Gruppe '{group_name}'"
            )
            created_groups_info.append(
                {"group_name": group_name, "status": "failed", "error": str(e)}
            )
    return created_groups_info

create_ticket(customer_email, subject, queue, body, priority_id=2, type_id_incident=2)

Erstellt ein neues Ticket in Zammad.

Parameters:

Name Type Description Default
customer_id int

Die ID des Kunden, für den das Ticket erstellt wird.

required
subject str

Der Betreff des Tickets.

required
queue str

Der Name der Ticket-Warteschlange (Gruppe).

required
body str

Der Hauptinhalt des Tickets (erste Artikel).

required
priority_id int

Die ID der Priorität des Tickets.

2
type_id_incident int

Die ID des Ticket-Typs (z.B. Incident).

2

Returns:

Name Type Description
dict

Das erstellte Ticket-Objekt, oder None bei einem Fehler.

Source code in src\utils\zammad_api.py
def create_ticket(
    self,
    customer_email,
    subject,
    queue,
    body,
    priority_id=2,
    type_id_incident=2,
):
    """
    Erstellt ein neues Ticket in Zammad.

    Args:
        customer_id (int): Die ID des Kunden, für den das Ticket erstellt wird.
        subject (str): Der Betreff des Tickets.
        queue (str): Der Name der Ticket-Warteschlange (Gruppe).
        body (str): Der Hauptinhalt des Tickets (erste Artikel).
        priority_id (int): Die ID der Priorität des Tickets.
        type_id_incident (int): Die ID des Ticket-Typs (z.B. Incident).

    Returns:
        dict: Das erstellte Ticket-Objekt, oder None bei einem Fehler.
    """

    customer_id = self.find_or_create_customer(customer_email)

    try:
        print("Versuche, Ticket zu erstellen...")
        ticket_params = {
            "title": subject,
            "group": queue,
            "customer_id": customer_id,
            "priority_id": priority_id,
            "type_id": type_id_incident,
            "article": {
                "body": body,
                # Typ wird auf "web" gesetzt, weil keine Mailadresse an mein System
                # angebunden ist (eigentlich bedeutet "web", dass Leute ihr Ticket
                # über die Zammad-Website erstellt haben)
                "type": "web",
                "internal": False,
                "sender": "Customer",
            },
        }

        new_ticket = self.client.ticket.create(ticket_params)
        print(f"Ticket erfolgreich erstellt mit ID: {new_ticket['id']}.")
        return new_ticket
    except Exception as e:
        self._handle_api_error(
            e, "Ein Fehler ist aufgetreten beim Erstellen des Tickets"
        )
        return None

find_or_create_customer(email, firstname_default='Imported', lastname_default='User')

Sucht einen Kunden anhand der E-Mail-Adresse. Wenn nicht gefunden, wird ein neuer Kunde erstellt.

Parameters:

Name Type Description Default
email str

Die E-Mail-Adresse des Kunden.

required
firstname_default str

Der Standard-Vorname für einen neuen Kunden.

'Imported'
lastname_default str

Der Standard-Nachname für einen neuen Kunden.

'User'

Returns:

Name Type Description
int

Die ID des gefundenen oder neu erstellten Kunden. None: Wenn ein Fehler

auftritt.

Source code in src\utils\zammad_api.py
def find_or_create_customer(
    self, email, firstname_default="Imported", lastname_default="User"
):
    """
    Sucht einen Kunden anhand der E-Mail-Adresse. Wenn nicht gefunden, wird ein
    neuer Kunde erstellt.

    Args:
        email (str): Die E-Mail-Adresse des Kunden.
        firstname_default (str): Der Standard-Vorname für einen neuen Kunden.
        lastname_default (str): Der Standard-Nachname für einen neuen Kunden.

    Returns:
        int: Die ID des gefundenen oder neu erstellten Kunden. None: Wenn ein Fehler
        auftritt.
    """
    try:
        print(f"Suche nach Kunde mit E-Mail: {email}...")
        # Die Suche nach 'email:<email>' ist oft präziser
        search_query = f"email:{email}"
        users = self.client.user.search({search_query})

        found_user = None
        if users:
            for user in users:  # Sicherstellen, dass die E-Mail exakt übereinstimmt
                if "email" in user and user["email"].lower() == email.lower():
                    found_user = user
                    break

        if found_user:
            customer_id = found_user["id"]
            print(f"Kunde gefunden mit ID: {customer_id}")
            return customer_id
        else:
            print(f"Kunde nicht gefunden. Erstelle neuen Kunden: {email}")
            user_params = {
                "firstname": firstname_default,
                "lastname": lastname_default,
                "email": email,
                "login": email,
                "roles": ["Customer"],
                "active": True,
            }
            new_customer = self.client.user.create(user_params)
            customer_id = new_customer["id"]
            print(f"Neuer Kunde erstellt mit ID: {customer_id}")
            return customer_id

    except Exception as e:
        self._handle_api_error(
            e, f"Fehler beim Suchen oder Erstellen des Kunden ({email})"
        )
        return None

— André Gasch