API Help
Problem reported by J. LaDow - Today at 10:36 AM
Resolved
So needless to say, this isn't my first rodeo. That said, an API implementation we were using has changed (understandable as we were on an older build that was pre .NET Core).

So we have tried to adapt our implementation, and for whatever reason, cannot manage to "verify if an email address exists" on the server using the API. In all methods, we are successfully authenticated - and no matter what we use, we get "domain not found".

Methods tried:
1)
endpoint: api/v1/settings/domain/get-user
method: POST
submitted data with just "email" and "email" + "clientId" (with clientId being either empty string or random data)
user logged in was a "domain administrator"

2)
endpoint: api/v1/settings/domain/user/{0}
method: GET
submitted email address formatted to request URL.
user logged in as "domain administrator"

3)
endpoint: api/v1/settings/sysadmin/get-user
method: POST
submitted data with just "email" and "email" + "clientId" (with clientId being either empty string or random data)
user logged in as system administrator

4)
endpoint: api/v1/settings/sysadmin/user/{0}
method: GET
submitted email address formatted to request URL.
user logged in as system administrator

ALL tests result in "domain not found" -- using multiple domains serviced on the server. 

This seems to point to something wrong with our installation, but everything else involved is working. Just can't find "users" via the API because it can't seem to find any of our domains.

MailEnable survivor / convert --
Sabatino Replied
Autenticazione    POST    /api/v1/auth/authenticate-user
Lista domini    GET    /api/v1/settings/sysadmin/domains
Settings dominio    GET    /api/v1/settings/domain/domain + header x-smartermaildomain


import requests
import json

# --- Configurazione ---
SMARTERMAIL_URL = "https://mail.tuoserver.com"  # URL del tuo server SmarterMail
USERNAME = "admin"                                # Username System Administrator
PASSWORD = "la_tua_password"                      # Password


def authenticate(server_url: str, username: str, password: str) -> str | None:
    """
    Autentica l'utente su SmarterMail e restituisce il token di accesso.
    Endpoint: POST /api/v1/auth/authenticate-user
    """
    auth_url = f"{server_url.rstrip('/')}/api/v1/auth/authenticate-user"
    
    payload = {
        "username": username,
        "password": password
    }
    
    response = requests.post(auth_url, json=payload, verify=True)
    
    if response.status_code != 200:
        print(f"Errore autenticazione: HTTP {response.status_code} - {response.text}")
        return None
    
    data = response.json()
    
    # Il token puo' essere in "accessToken" o "access_token"
    token = data.get("accessToken") or data.get("access_token")
    
    if not token:
        print(f"Token non trovato nella risposta: {data}")
        return None
    
    print("Autenticazione riuscita!")
    return token


def get_domains_list(server_url: str, token: str) -> list[dict] | None:
    """
    Recupera l'elenco completo dei domini.
    Endpoint: GET /api/v1/settings/sysadmin/domains
    Richiede credenziali System Administrator.
    """
    domains_url = f"{server_url.rstrip('/')}/api/v1/settings/sysadmin/domains"
    
    headers = {
        "Authorization": f"Bearer {token}"
    }
    
    response = requests.get(domains_url, headers=headers, verify=True)
    
    if response.status_code != 200:
        print(f"Errore recupero domini: HTTP {response.status_code} - {response.text}")
        return None
    
    data = response.json()
    
    # Verifica successo
    if data.get("success") is False:
        print(f"Errore API: {data.get('message', 'Errore sconosciuto')}")
        return None
    
    # I domini possono essere in "data", "results" o "domains"
    domains = data.get("data") or data.get("results") or data.get("domains") or []
    
    return domains


def get_domain_settings(server_url: str, token: str, domain_name: str) -> dict | None:
    """
    Recupera le impostazioni di un dominio (incluso blocco paesi).
    Endpoint: GET /api/v1/settings/domain/domain
    Usa l'header x-smartermaildomain per specificare il dominio.
    """
    settings_url = f"{server_url.rstrip('/')}/api/v1/settings/domain/domain"
    
    headers = {
        "Authorization": f"Bearer {token}",
        "x-smartermaildomain": domain_name
    }
    
    response = requests.get(settings_url, headers=headers, verify=True)
    
    if response.status_code != 200:
        return None
    
    return response.json()


def format_bytes(size_bytes: int) -> str:
    """Formatta i bytes in formato leggibile (come FormatBytes nel .cshtml)."""
    if size_bytes < 1024:
        return f"{size_bytes} B"
    elif size_bytes < 1024 * 1024:
        return f"{size_bytes / 1024:.2f} KB"
    elif size_bytes < 1024 * 1024 * 1024:
        return f"{size_bytes / (1024 * 1024):.2f} MB"
    else:
        return f"{size_bytes / (1024 * 1024 * 1024):.2f} GB"


def print_domains_table(domains: list[dict]) -> None:
    """Stampa la tabella dei domini (replica la visualizzazione della pagina Razor)."""
    
    # Ordina per nome
    domains_sorted = sorted(domains, key=lambda d: d.get("name", "").lower())
    
    # Statistiche aggregate
    total_users = sum(d.get("userCount", 0) for d in domains_sorted)
    total_disk = sum(d.get("size", 0) for d in domains_sorted)
    enabled = sum(1 for d in domains_sorted if d.get("isEnabled", False))
    disabled = len(domains_sorted) - enabled
    
    print("\n" + "=" * 90)
    print("ELENCO DOMINI SMARTERMAIL")
    print("=" * 90)
    
    # Statistiche
    print(f"\n  Domini totali: {len(domains_sorted)}")
    print(f"  Utenti totali: {total_users:,}")
    print(f"  Spazio disco totale: {format_bytes(total_disk)}")
    print(f"  Attivi: {enabled} | Disabilitati: {disabled}")
    
    # Header tabella
    print("\n" + "-" * 90)
    print(f"{'Dominio':<30} {'Stato':<10} {'Utenti':<12} {'Disco':<15} {'Quota':<15} {'Alias':<8}")
    print("-" * 90)
    
    # Righe
    for d in domains_sorted:
        name = d.get("name", "N/A")
        is_enabled = d.get("isEnabled", False)
        status = "Attivo" if is_enabled else "Disab."
        
        user_count = d.get("userCount", 0)
        user_limit = d.get("userLimit", 0)
        users_str = f"{user_count}" + (f"/{user_limit}" if user_limit > 0 else "")
        
        size = d.get("size", 0)
        disk_str = format_bytes(size)
        
        max_size = d.get("maxSize", 0)
        if max_size > 0:
            quota_str = format_bytes(max_size)
            pct = (size / max_size * 100) if max_size > 0 else 0
            quota_str += f" ({pct:.1f}%)"
        else:
            quota_str = "Illimitato"
        
        alias_count = d.get("aliasCount", 0)
        
        print(f"  {name:<28} {status:<10} {users_str:<12} {disk_str:<15} {quota_str:<15} {alias_count:<8}")
    
    print("-" * 90)
    print(f"  {len(domains_sorted)} domini visualizzati\n")


def main():
    # 1. Autenticazione
    print("Connessione a SmarterMail...")
    token = authenticate(SMARTERMAIL_URL, USERNAME, PASSWORD)
    if not token:
        return
    
    # 2. Recupero elenco domini
    print("Recupero elenco domini...")
    domains = get_domains_list(SMARTERMAIL_URL, token)
    if domains is None:
        return
    
    # 3. Visualizzazione tabella
    print_domains_table(domains)
    
    # 4. (Opzionale) Recupero impostazioni blocco paesi per ogni dominio
    print("Recupero impostazioni blocco paesi...")
    for d in sorted(domains, key=lambda x: x.get("name", "")):
        domain_name = d.get("name", "")
        settings = get_domain_settings(SMARTERMAIL_URL, token, domain_name)
        
        if settings:
            blocked = settings.get("authBlockedCountrySettings") or {}
            list_type = blocked.get("listType", 0)
            countries = blocked.get("countryList", [])
            is_active = blocked.get("isActive", False)
            
            if is_active and countries:
                mode = "Blocca" if list_type == 1 else "Accetta solo"
                print(f"  {domain_name}: {mode} -> {', '.join(countries)}")
            else:
                print(f"  {domain_name}: Nessun blocco paese")


if __name__ == "__main__":
    main()
This should work.

I extracted it from my analysis program, which works fine.





Sabatino Traini Chief Information Officer Genial s.r.l. Martinsicuro - Italy
Sabatino Replied
Marked As Resolution
Operazione    Metodo    Endpoint    Note
Autenticazione    POST    /api/v1/auth/authenticate-user    Body: {username, password}
Lista domini    GET    /api/v1/settings/sysadmin/domains    Bearer token
Utenti dominio    POST    /api/v1/settings/domain/account-list-search    Header x-smartermaildomain

import requests
import json
from datetime import datetime

# --- Configurazione ---
SMARTERMAIL_URL = "https://mail.tuoserver.com"  # URL del tuo server SmarterMail
USERNAME = "admin"                                # Username System Administrator
PASSWORD = "la_tua_password"                      # Password


def authenticate(server_url: str, username: str, password: str) -> str | None:
    """
    Autentica l'utente su SmarterMail e restituisce il token di accesso.
    Endpoint: POST /api/v1/auth/authenticate-user
    """
    auth_url = f"{server_url.rstrip('/')}/api/v1/auth/authenticate-user"

    payload = {
        "username": username,
        "password": password
    }

    response = requests.post(auth_url, json=payload, verify=True)

    if response.status_code != 200:
        print(f"Errore autenticazione: HTTP {response.status_code} - {response.text}")
        return None

    data = response.json()

    # Il token puo' essere in "accessToken" o "access_token"
    token = data.get("accessToken") or data.get("access_token")

    if not token:
        print(f"Token non trovato nella risposta: {data}")
        return None

    print("Autenticazione riuscita!")
    return token


def get_domains_list(server_url: str, token: str) -> list[dict] | None:
    """
    Recupera l'elenco completo dei domini.
    Endpoint: GET /api/v1/settings/sysadmin/domains
    Richiede credenziali System Administrator.
    """
    domains_url = f"{server_url.rstrip('/')}/api/v1/settings/sysadmin/domains"

    headers = {
        "Authorization": f"Bearer {token}"
    }

    response = requests.get(domains_url, headers=headers, verify=True)

    if response.status_code != 200:
        print(f"Errore recupero domini: HTTP {response.status_code} - {response.text}")
        return None

    data = response.json()

    # Verifica successo
    if data.get("success") is False:
        print(f"Errore API: {data.get('message', 'Errore sconosciuto')}")
        return None

    # I domini possono essere in "data", "results" o "domains"
    domains = data.get("data") or data.get("results") or data.get("domains") or []

    return domains


def get_domain_users(server_url: str, token: str, domain_name: str, take: int = 10000) -> list[dict] | None:
    """
    Recupera l'elenco degli utenti di un dominio.
    Endpoint: POST /api/v1/settings/domain/account-list-search
    Usa l'header x-smartermaildomain per specificare il dominio target.
    searchFlags=["users"] per ottenere solo utenti (esclusi alias e mailing list).
    """
    users_url = f"{server_url.rstrip('/')}/api/v1/settings/domain/account-list-search"

    headers = {
        "Authorization": f"Bearer {token}",
        "x-smartermaildomain": domain_name,
        "Content-Type": "application/json"
    }

    # Body della richiesta - stesso formato usato in SmarterMailApiService.cs
    payload = {
        "skip": 0,
        "take": take,
        "search": "",
        "sortField": "bytesAllowed",
        "sortDescending": True,
        "searchFlags": ["users"]  # IMPORTANTE: senza questo ritorna 0 risultati
    }

    response = requests.post(users_url, json=payload, headers=headers, verify=True)

    if response.status_code != 200:
        print(f"  Errore recupero utenti {domain_name}: HTTP {response.status_code}")
        return None

    data = response.json()

    if data.get("success") is False:
        print(f"  Errore API utenti {domain_name}: {data.get('message')}")
        return None

    users = data.get("results", [])

    # Filtra: includi solo User (1), DomainAdmin (2), PrimaryDomainAdmin (3)
    # Escludi Alias (4) e MailingList (5)
    filtered = [u for u in users if u.get("accountType", 0) in (1, 2, 3)]

    return filtered


def format_bytes(size_bytes: int) -> str:
    """Formatta i bytes in formato leggibile."""
    if size_bytes < 1024:
        return f"{size_bytes} B"
    elif size_bytes < 1024 * 1024:
        return f"{size_bytes / 1024:.2f} KB"
    elif size_bytes < 1024 * 1024 * 1024:
        return f"{size_bytes / (1024 * 1024):.2f} MB"
    else:
        return f"{size_bytes / (1024 * 1024 * 1024):.2f} GB"


def get_status_name(status: int) -> str:
    """Converte il codice status numerico in stringa leggibile."""
    status_map = {
        0: "Disabilitato",
        1: "Attivo",
        2: "Bloccato"
    }
    return status_map.get(status, f"Sconosciuto({status})")


def get_account_type_name(account_type: int) -> str:
    """Converte il codice accountType in stringa leggibile."""
    type_map = {
        1: "User",
        2: "DomainAdmin",
        3: "PrimaryDomainAdmin",
        4: "Alias",
        5: "MailingList"
    }
    return type_map.get(account_type, f"Tipo({account_type})")


def print_domains_table(domains: list[dict]) -> None:
    """Stampa la tabella dei domini."""
    domains_sorted = sorted(domains, key=lambda d: d.get("name", "").lower())

    total_users = sum(d.get("userCount", 0) for d in domains_sorted)
    total_disk = sum(d.get("size", 0) for d in domains_sorted)
    enabled = sum(1 for d in domains_sorted if d.get("isEnabled", False))

    print("\n" + "=" * 90)
    print("ELENCO DOMINI SMARTERMAIL")
    print("=" * 90)
    print(f"\n  Domini totali: {len(domains_sorted)}  |  Utenti totali: {total_users:,}")
    print(f"  Spazio disco totale: {format_bytes(total_disk)}  |  Attivi: {enabled}  |  Disabilitati: {len(domains_sorted) - enabled}")

    print("\n" + "-" * 90)
    print(f"  {'Dominio':<30} {'Stato':<10} {'Utenti':<12} {'Disco':<15} {'Quota':<15}")
    print("-" * 90)

    for d in domains_sorted:
        name = d.get("name", "N/A")
        status = "Attivo" if d.get("isEnabled", False) else "Disab."
        user_count = d.get("userCount", 0)
        user_limit = d.get("userLimit", 0)
        users_str = f"{user_count}" + (f"/{user_limit}" if user_limit > 0 else "")
        size = d.get("size", 0)
        max_size = d.get("maxSize", 0)
        quota_str = format_bytes(max_size) if max_size > 0 else "Illimitato"

        print(f"  {name:<30} {status:<10} {users_str:<12} {format_bytes(size):<15} {quota_str:<15}")

    print("-" * 90)


def print_users_table(domain_name: str, users: list[dict]) -> None:
    """Stampa la tabella utenti di un dominio."""
    print(f"\n  {'Email':<35} {'Nome':<20} {'Stato':<12} {'Tipo':<18} {'Disco usato':<15} {'Quota':<15} {'Ultimo login':<12}")
    print("  " + "-" * 127)

    for u in sorted(users, key=lambda x: x.get("userName", "")):
        email = f"{u.get('userName', '?')}@{domain_name}"
        display_name = u.get("displayName", "") or ""
        if len(display_name) > 18:
            display_name = display_name[:17] + "."
        status = get_status_name(u.get("status", 0))
        acc_type = get_account_type_name(u.get("accountType", 1))
        bytes_used = u.get("bytesUsed", 0)
        bytes_allowed = u.get("bytesAllowed")
        quota_str = format_bytes(bytes_allowed) if bytes_allowed else "Illimitato"

        last_login = u.get("lastLoginTime") or u.get("lastLogin") or ""
        if last_login:
            try:
                dt = datetime.fromisoformat(last_login.replace("Z", "+00:00"))
                last_login = dt.strftime("%d/%m/%Y")
            except (ValueError, AttributeError):
                last_login = str(last_login)[:10]

        # Protocolli abilitati
        protocols = []
        if u.get("isWebmailEnabled"): protocols.append("Web")
        if u.get("isImapEnabled"): protocols.append("IMAP")
        if u.get("isPopEnabled"): protocols.append("POP")
        if u.get("isEasEnabled"): protocols.append("EAS")
        if u.get("isMapiEwsEnabled"): protocols.append("MAPI/EWS")

        print(f"  {email:<35} {display_name:<20} {status:<12} {acc_type:<18} {format_bytes(bytes_used):<15} {quota_str:<15} {last_login:<12}")

    total_bytes = sum(u.get("bytesUsed", 0) for u in users)
    active = sum(1 for u in users if u.get("status", 0) == 1)
    print(f"\n  Totale: {len(users)} utenti ({active} attivi) - Disco usato: {format_bytes(total_bytes)}")


def main():
    # 1. Autenticazione
    print("Connessione a SmarterMail...")
    token = authenticate(SMARTERMAIL_URL, USERNAME, PASSWORD)
    if not token:
        return

    # 2. Recupero elenco domini
    print("Recupero elenco domini...")
    domains = get_domains_list(SMARTERMAIL_URL, token)
    if domains is None:
        return

    print_domains_table(domains)

    # 3. Recupero utenti per ogni dominio (come fa ElencoUtenti.cshtml.cs)
    # Filtra solo domini con utenti, ordinati per nome
    domains_with_users = sorted(
        [d for d in domains if d.get("userCount", 0) > 0],
        key=lambda d: d.get("name", "").lower()
    )

    print(f"\n{'=' * 90}")
    print(f"ELENCO UTENTI PER DOMINIO ({len(domains_with_users)} domini con utenti)")
    print(f"{'=' * 90}")

    grand_total_users = 0
    grand_total_bytes = 0

    for i, domain in enumerate(domains_with_users, 1):
        domain_name = domain.get("name", "")
        expected_count = domain.get("userCount", 0)
        print(f"\n[{i}/{len(domains_with_users)}] Caricamento {domain_name} (~{expected_count} utenti)...")

        users = get_domain_users(SMARTERMAIL_URL, token, domain_name)

        if users is not None:
            print_users_table(domain_name, users)
            grand_total_users += len(users)
            grand_total_bytes += sum(u.get("bytesUsed", 0) for u in users)
        else:
            print(f"  Impossibile caricare gli utenti di {domain_name}")

    print(f"\n{'=' * 90}")
    print(f"RIEPILOGO: {grand_total_users} utenti totali - Disco usato: {format_bytes(grand_total_bytes)}")
    print(f"{'=' * 90}\n")


if __name__ == "__main__":
    main()

Sabatino Traini Chief Information Officer Genial s.r.l. Martinsicuro - Italy
J. LaDow Replied
THANK YOU!

The key was the missing header "x-smartermaildomain" which doesn't seem to be documented anywhere.
MailEnable survivor / convert --
Sabatino Replied
I had difficulty with it too. I don't remember if I found it here or in an example on GitHub.
Sabatino Traini Chief Information Officer Genial s.r.l. Martinsicuro - Italy

Reply to Thread

Enter the verification text