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