Building a Custom Lead Gen Dashboard with the Easy Email Finder API
Published February 10, 2026
Why Build a Custom Dashboard?
While the Easy Email Finder web app is great for individual use, teams often need a shared dashboard with custom workflows, usage tracking, and role-based access. With the API, you can build exactly what your team needs.
What We Are Building
Our dashboard will have three main features:
- Search panel: Find businesses by keyword and location, then enrich them
- Results table: View, filter, and export enriched leads
- Usage tracker: Monitor credit consumption and API usage over time
Backend: Express API Proxy
Never expose your API key to the frontend. Instead, build a thin backend that proxies requests to the Easy Email Finder API.
// server.ts
import express from 'express';
import cors from 'cors';
import 'dotenv/config';
const app = express();
app.use(cors());
app.use(express.json());
const EEF_KEY = process.env.EEF_API_KEY;
const EEF_BASE = "https://easyemailfinder.com/api/v1";
const eefHeaders = {
"Authorization": `Bearer ${EEF_KEY}`,
"Content-Type": "application/json"
};
// Search endpoint
app.post('/api/search', async (req, res) => {
try {
const resp = await fetch(`${EEF_BASE}/search`, {
method: "POST",
headers: eefHeaders,
body: JSON.stringify(req.body)
});
const data = await resp.json();
res.json(data);
} catch (err) {
res.status(500).json({ error: "Search failed" });
}
});
// Enrich batch endpoint
app.post('/api/enrich', async (req, res) => {
try {
const resp = await fetch(`${EEF_BASE}/enrich-batch`, {
method: "POST",
headers: eefHeaders,
body: JSON.stringify(req.body)
});
const data = await resp.json();
res.json(data);
} catch (err) {
res.status(500).json({ error: "Enrichment failed" });
}
});
// Balance endpoint
app.get('/api/balance', async (req, res) => {
try {
const resp = await fetch(`${EEF_BASE}/balance`, {
headers: { "Authorization": `Bearer ${EEF_KEY}` }
});
const data = await resp.json();
res.json(data);
} catch (err) {
res.status(500).json({ error: "Could not fetch balance" });
}
});
// Usage endpoint
app.get('/api/usage', async (req, res) => {
try {
const resp = await fetch(`${EEF_BASE}/usage`, {
headers: { "Authorization": `Bearer ${EEF_KEY}` }
});
const data = await resp.json();
res.json(data);
} catch (err) {
res.status(500).json({ error: "Could not fetch usage" });
}
});
app.listen(3001, () => console.log("API proxy running on port 3001"));
Frontend: React Dashboard
Here is the core React component for the search and results panel:
// SearchPanel.tsx
import { useState } from 'react';
interface Lead {
name: string;
email: string;
website: string;
phone: string;
address: string;
}
export function SearchPanel() {
const [query, setQuery] = useState('');
const [location, setLocation] = useState('');
const [leads, setLeads] = useState<Lead[]>([]);
const [loading, setLoading] = useState(false);
const [credits, setCredits] = useState<number | null>(null);
async function handleSearch() {
setLoading(true);
try {
// Step 1: Search
const searchRes = await fetch('/api/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, location, mode: 'local' })
});
const { results } = await searchRes.json();
// Step 2: Enrich
const websites = results
.filter((r: any) => r.website)
.map((r: any) => r.website);
if (websites.length > 0) {
const enrichRes = await fetch('/api/enrich', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ websites: websites.slice(0, 20) })
});
const enriched = await enrichRes.json();
setLeads(enriched.results || []);
}
// Update balance
const balRes = await fetch('/api/balance');
const { credits: c } = await balRes.json();
setCredits(c);
} catch (err) {
console.error('Search failed:', err);
} finally {
setLoading(false);
}
}
function exportCSV() {
const header = 'Name,Email,Website,Phone,Address\n';
const rows = leads
.filter(l => l.email)
.map(l => `"${l.name}","${l.email}","${l.website}","${l.phone}","${l.address}"`)
.join('\n');
const blob = new Blob([header + rows], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `leads_${query}_${location}.csv`;
a.click();
}
return (
<div>
<h1>Lead Generator</h1>
{credits !== null && <p>Credits: {credits}</p>}
<input value={query} onChange={e => setQuery(e.target.value)}
placeholder="Business type (e.g., dentists)" />
<input value={location} onChange={e => setLocation(e.target.value)}
placeholder="Location (e.g., Austin, TX)" />
<button onClick={handleSearch} disabled={loading}>
{loading ? 'Searching...' : 'Find Leads'}
</button>
{leads.length > 0 && <button onClick={exportCSV}>Export CSV</button>}
<table>
<thead><tr>
<th>Name</th><th>Email</th><th>Phone</th><th>Website</th>
</tr></thead>
<tbody>
{leads.filter(l => l.email).map((lead, i) => (
<tr key={i}>
<td>{lead.name}</td>
<td>{lead.email}</td>
<td>{lead.phone}</td>
<td><a href={lead.website}>{lead.website}</a></td>
</tr>
))}
</tbody>
</table>
</div>
);
}
Adding Usage Analytics
Use the free /usage endpoint to build a chart showing credit consumption over time. Pair this with a charting library like Recharts to visualize trends. See our guide on monitoring API usage for implementation details.
Security Considerations
- Never expose your Easy Email Finder API key in frontend code
- Add authentication to your Express proxy (session-based or JWT)
- Implement per-user rate limiting on your proxy to prevent abuse
- Log all searches for audit purposes
Deployment
Deploy the Express backend to any Node.js platform (Railway, Render, or a VPS). The React frontend can be served as static files from the same server or deployed to Vercel/Netlify. For more on API architecture, refer to the developer docs. For the backend enrichment patterns, check our Node.js pipeline guide.
Ready to find business emails?
Try Easy Email Finder free, get 5 credits to start.
Start Finding Emails