Cleanbox
Features Helpdesk Blog Pricing Contact
Sign in Start free trial
api developer tutorial

Building a Custom Email Dashboard with the Cleanbox API

The Cleanbox dashboard gives you a solid overview of your email activity. But what if you want to embed email metrics in your existing admin panel, create a wall-mounted team dashboard, or build custom visualizations that the built-in dashboard does not offer?

The API exposes everything you need. In this guide, we build a lightweight dashboard that pulls data from multiple API endpoints and renders it in a clean, auto-refreshing interface.

The data sources

WidgetEndpointData
Usage gaugeGET /v1/teamWeekly used vs limit, alias count, mailbox count
Plan infoGET /v1/team/subscriptionPlan name, pricing, specifications
Quarantine counterGET /v1/quarantinePending review count
Recent messagesGET /v1/messagesLatest emails with status and spam score
Active aliasesGET /v1/addresses?type=aliasAlias list with labels and status
Top contactsGET /v1/contacts?sort=emailCountMost active senders
Domain statusGET /v1/domainsVerification and active status
Relay overviewGET /v1/relayProtected domains and addresses
Cloud usageGET /v1/cloudStorage used vs limit

Backend: API aggregator

To avoid CORS issues and keep the API key server-side, create a thin backend that aggregates multiple API calls:

# dashboard_api.py
from flask import Flask, jsonify
import requests

app = Flask(__name__)

API_BASE = "https://api.cleanbox.app/v1"
API_KEY  = "your_api_key_here"
HEADERS  = {"Authorization": f"Bearer {API_KEY}"}

def api_get(path, params=None):
    r = requests.get(f"{API_BASE}{path}", headers=HEADERS, params=params)
    return r.json() if r.ok else {}

@app.route("/api/dashboard")
def dashboard():
    """Aggregate all dashboard data in a single response."""
    team = api_get("/team")
    subscription = api_get("/team/subscription")
    quarantine = api_get("/quarantine", {"per_page": 1})
    messages = api_get("/messages", {"per_page": 10})
    addresses = api_get("/addresses", {"type": "alias", "per_page": 100})
    contacts = api_get("/contacts", {"sort": "emailCount", "dir": "desc", "per_page": 10})
    domains = api_get("/domains")
    relay = api_get("/relay")
    cloud = api_get("/cloud")

    return jsonify({
        "team": team,
        "subscription": subscription,
        "quarantine_count": quarantine.get("total_pages", 0) * 50,
        "recent_messages": messages.get("data", []),
        "aliases": addresses.get("data", []),
        "top_contacts": contacts.get("data", []),
        "domains": domains.get("data", []),
        "relay": relay.get("data", []),
        "cloud": cloud.get("storage", {})
    })

if __name__ == "__main__":
    app.run(port=5000)

This single endpoint aggregates 9 API calls into one response. The frontend makes one request and gets everything it needs.

Frontend: Dashboard widgets

A minimal HTML + vanilla JavaScript dashboard that auto-refreshes:

<!DOCTYPE html>
<html>
<head>
    <title>Email Dashboard</title>
    <style>
        body { font-family: -apple-system, sans-serif; background: #0f1117; color: #e2e8f0; padding: 24px; }
        .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 16px; }
        .card { background: #1a1e2e; border-radius: 12px; padding: 20px; }
        .card h3 { color: #94a3b8; font-size: 12px; text-transform: uppercase; letter-spacing: 0.05em; margin: 0 0 12px; }
        .big-number { font-size: 36px; font-weight: 700; }
        .sub { font-size: 13px; color: #64748b; margin-top: 4px; }
        .bar { height: 6px; background: #1e293b; border-radius: 3px; margin-top: 8px; }
        .bar-fill { height: 100%; background: #3b82f6; border-radius: 3px; }
        .list { font-size: 14px; }
        .list-item { padding: 8px 0; border-bottom: 1px solid #1e293b; display: flex; justify-content: space-between; }
        .badge { font-size: 11px; padding: 2px 8px; border-radius: 4px; }
        .badge-delivered { background: #064e3b; color: #34d399; }
        .badge-denied { background: #1e1b4b; color: #818cf8; }
        .badge-failed { background: #4a1d2e; color: #f87171; }
    </style>
</head>
<body>
    <h1 style="margin-bottom:24px">Email Dashboard</h1>
    <div class="grid" id="dashboard">Loading...</div>

    <script>
    async function refresh() {
        const res = await fetch("/api/dashboard");
        const d = await res.json();

        const usage = d.team?.usage || {};
        const usagePct = usage.weekly_limit > 0
            ? Math.round((usage.weekly_used / usage.weekly_limit) * 100) : 0;

        const limits = d.team?.limits || {};
        const cloud = d.cloud || {};
        const cloudPct = cloud.limit > 0
            ? Math.round((cloud.used / cloud.limit) * 100) : 0;

        document.getElementById("dashboard").innerHTML = `
            <div class="card">
                <h3>Weekly Usage</h3>
                <div class="big-number">${usagePct}%</div>
                <div class="sub">${usage.weekly_used || 0} / ${usage.weekly_limit || 0} emails</div>
                <div class="bar"><div class="bar-fill" style="width:${usagePct}%"></div></div>
            </div>

            <div class="card">
                <h3>Resources</h3>
                <div class="list">
                    <div class="list-item"><span>Aliases</span><span>${limits.aliases?.used || 0} / ${limits.aliases?.limit || 0}</span></div>
                    <div class="list-item"><span>Mailboxes</span><span>${limits.mailboxes?.used || 0} / ${limits.mailboxes?.limit || 0}</span></div>
                    <div class="list-item"><span>Domains</span><span>${limits.domains?.used || 0} / ${limits.domains?.limit || 0}</span></div>
                    <div class="list-item"><span>Filters</span><span>${limits.filters?.used || 0} / ${limits.filters?.limit || 0}</span></div>
                </div>
            </div>

            <div class="card">
                <h3>Quarantine</h3>
                <div class="big-number">${d.quarantine_count}</div>
                <div class="sub">messages pending review</div>
            </div>

            <div class="card">
                <h3>Cloud Storage</h3>
                <div class="big-number">${cloudPct}%</div>
                <div class="sub">${formatBytes(cloud.used || 0)} / ${formatBytes(cloud.limit || 0)}</div>
                <div class="bar"><div class="bar-fill" style="width:${cloudPct}%"></div></div>
            </div>

            <div class="card" style="grid-column: span 2">
                <h3>Recent Messages</h3>
                <div class="list">
                    ${d.recent_messages.map(m => `
                        <div class="list-item">
                            <span>${escapeHtml(m.from_name || m.from_address)} &mdash; ${escapeHtml(m.subject || "(no subject)")}</span>
                            <span class="badge badge-${m.status}">${m.status}</span>
                        </div>
                    `).join("")}
                </div>
            </div>

            <div class="card" style="grid-column: span 2">
                <h3>Top Contacts</h3>
                <div class="list">
                    ${d.top_contacts.map(c => `
                        <div class="list-item">
                            <span>${escapeHtml(c.name || c.email)} <span style="color:#64748b">${c.category?.name || ""}</span></span>
                            <span>${c.email_count} emails</span>
                        </div>
                    `).join("")}
                </div>
            </div>
        `;
    }

    function formatBytes(bytes) {
        if (bytes === 0) return "0 B";
        const k = 1024, sizes = ["B", "KB", "MB", "GB"];
        const i = Math.floor(Math.log(bytes) / Math.log(k));
        return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
    }

    function escapeHtml(str) {
        const div = document.createElement("div");
        div.textContent = str;
        return div.innerHTML;
    }

    // Initial load + auto-refresh every 60 seconds
    refresh();
    setInterval(refresh, 60000);
    </script>
</body>
</html>

What each widget shows

  • Weekly Usage: Circular gauge with percentage and raw numbers from /v1/team
  • Resources: Alias, mailbox, domain, and filter counts vs. plan limits
  • Quarantine: Number of messages pending review — a growing number means you need to check quarantine
  • Cloud Storage: Used vs. available storage from /v1/cloud
  • Recent Messages: Last 10 messages with delivery status badges
  • Top Contacts: Most active senders with email count and category

Deployment options

  • Local: python3 dashboard_api.py and open http://localhost:5000
  • Internal server: Deploy with gunicorn behind nginx for your team
  • Wall display: Open in a kiosk-mode browser on a wall-mounted screen
  • Embed: Serve the widgets as an iframe in your existing admin panel

Extending further

  • Relay status: Add a widget showing relay domains and their address counts from /v1/relay
  • Filter activity: List active filters from /v1/filters with toggle buttons using PATCH /v1/filters/{uuid}/toggle
  • Quarantine actions: Add accept/reject buttons that call POST /v1/quarantine/accept and POST /v1/quarantine/reject
  • Historical trends: Store snapshots in a database and render charts showing weekly/monthly patterns

The Cleanbox API returns structured JSON for every resource. Anything you see in the dashboard, you can build yourself — styled your way, embedded where you need it.

For the complete endpoint reference, see the API documentation. For authentication setup, see API keys and developer access.

Ready to take control of your inbox?

Start protecting your email with Cleanbox — free plan available, no credit card required.

Get started free