diff -ruN a/README.md b/README.md
--- a/README.md	2026-08-19 08:47:39.256627514 +0000
+++ b/README.md	2026-08-19 08:47:39.280593534 +0000
@@ -72,6 +72,10 @@
    download.
 8. **Choose columns at download time** — pick a subset of columns in a
    dialog before exporting, independent of what's shown on screen.
+9. **Filter by email presence** — "All rows" / "Has email" / "Missing
+   email" in the toolbar, applied to both the table view and the download
+   (so you can pull just the leads that need enrichment, or just the ones
+   ready for outreach).
 
 ## Background processing & job history
 
diff -ruN a/backend/main.py b/backend/main.py
--- a/backend/main.py	2026-08-19 08:47:39.237526380 +0000
+++ b/backend/main.py	2026-08-19 08:47:39.260513894 +0000
@@ -386,7 +386,7 @@
     dataset: str = Query("clean"),
     category: str | None = Query(None),
     search: str | None = Query(None),
-    email_only: bool = Query(False),
+    email_filter: str = Query("all", pattern="^(all|has_email|no_email)$"),
     page: int = Query(1, ge=1),
     page_size: int = Query(50, ge=1, le=1000),
 ):
@@ -402,10 +402,13 @@
         where_clauses.append(f'"{cat_col}" = ?')
         params.append(category)
 
-    if email_only:
+    if email_filter != "all":
         email_col = _guess_email_col(con, parquet_path)
         if email_col:
-            where_clauses.append(f'"{email_col}" IS NOT NULL')
+            if email_filter == "has_email":
+                where_clauses.append(f'"{email_col}" IS NOT NULL')
+            else:  # no_email
+                where_clauses.append(f'"{email_col}" IS NULL')
 
     if search:
         str_cols = _string_columns(con, parquet_path)
@@ -497,7 +500,7 @@
     job_id: str,
     dataset: str = Query("clean"),
     category: str | None = Query(None),
-    email_only: bool = Query(False),
+    email_filter: str = Query("all", pattern="^(all|has_email|no_email)$"),
     columns: list[str] | None = Query(None),
     background_tasks: BackgroundTasks = None,
 ):
@@ -511,10 +514,13 @@
     if category and cat_col:
         where_clauses.append(f'"{cat_col}" = ?')
         params.append(category)
-    if email_only:
+    if email_filter != "all":
         email_col = _guess_email_col(con, parquet_path)
         if email_col:
-            where_clauses.append(f'"{email_col}" IS NOT NULL')
+            if email_filter == "has_email":
+                where_clauses.append(f'"{email_col}" IS NOT NULL')
+            else:  # no_email
+                where_clauses.append(f'"{email_col}" IS NULL')
     where_sql = f"WHERE {' AND '.join(where_clauses)}" if where_clauses else ""
 
     select_expr = _sanitized_select(con, parquet_path, columns=columns)
diff -ruN a/frontend/src/App.jsx b/frontend/src/App.jsx
--- a/frontend/src/App.jsx	2026-08-19 08:47:39.254353874 +0000
+++ b/frontend/src/App.jsx	2026-08-19 08:47:39.278964748 +0000
@@ -306,7 +306,7 @@
   const [dataset, setDataset] = useState('clean')
   const [category, setCategory] = useState('')
   const [search, setSearch] = useState('')
-  const [emailOnly, setEmailOnly] = useState(false)
+  const [emailFilter, setEmailFilter] = useState('all')
   const [page, setPage] = useState(1)
   const [pageSize] = useState(50)
   const [data, setData] = useState({ columns: [], rows: [], total: 0 })
@@ -321,15 +321,15 @@
   useEffect(() => {
     setLoading(true)
     const t = setTimeout(() => {
-      getData(jobId, { dataset, category, search, emailOnly, page, pageSize })
+      getData(jobId, { dataset, category, search, emailFilter, page, pageSize })
         .then(setData)
         .catch(() => {})
         .finally(() => setLoading(false))
     }, 250) // debounce search typing
     return () => clearTimeout(t)
-  }, [jobId, dataset, category, search, emailOnly, page, pageSize])
+  }, [jobId, dataset, category, search, emailFilter, page, pageSize])
 
-  useEffect(() => { setPage(1) }, [dataset, category, search, emailOnly])
+  useEffect(() => { setPage(1) }, [dataset, category, search, emailFilter])
   useEffect(() => { setSelectedColumns(null) }, [dataset]) // column set differs between clean/rejected
 
   function openColumnPicker() {
@@ -347,7 +347,7 @@
   function confirmDownload() {
     const cols = selectedColumns && selectedColumns.length ? selectedColumns : data.columns
     triggerDownload(
-      downloadUrl(jobId, { dataset, category, emailOnly, columns: cols }),
+      downloadUrl(jobId, { dataset, category, emailFilter, columns: cols }),
       `leads_${dataset}${category ? '_' + category : ''}.csv`
     )
     setShowColumnPicker(false)
@@ -401,10 +401,11 @@
             onChange={(e) => setSearch(e.target.value)}
           />
           {dataset === 'clean' && (
-            <label className="option checkbox inline">
-              <input type="checkbox" checked={emailOnly} onChange={(e) => setEmailOnly(e.target.checked)} />
-              <span>Email only</span>
-            </label>
+            <select value={emailFilter} onChange={(e) => setEmailFilter(e.target.value)}>
+              <option value="all">All rows</option>
+              <option value="has_email">Has email</option>
+              <option value="no_email">Missing email</option>
+            </select>
           )}
         </div>
         <div className="toolbar-right">
diff -ruN a/frontend/src/api.js b/frontend/src/api.js
--- a/frontend/src/api.js	2026-08-19 08:47:39.252431310 +0000
+++ b/frontend/src/api.js	2026-08-19 08:47:39.272289488 +0000
@@ -137,7 +137,7 @@
   return res.json()
 }
 
-export async function getData(jobId, { dataset = 'clean', category, search, emailOnly, page, pageSize }) {
+export async function getData(jobId, { dataset = 'clean', category, search, emailFilter, page, pageSize }) {
   const params = new URLSearchParams({
     dataset,
     page: String(page),
@@ -145,16 +145,16 @@
   })
   if (category) params.set('category', category)
   if (search) params.set('search', search)
-  if (emailOnly) params.set('email_only', 'true')
+  if (emailFilter && emailFilter !== 'all') params.set('email_filter', emailFilter)
   const res = await fetch(`/api/jobs/${jobId}/data?${params}`, { headers: headers(), credentials: 'include' })
   if (!res.ok) throw new Error('Could not fetch data')
   return res.json()
 }
 
-export function downloadUrl(jobId, { dataset = 'clean', category, emailOnly, columns }) {
+export function downloadUrl(jobId, { dataset = 'clean', category, emailFilter, columns }) {
   const params = new URLSearchParams({ dataset })
   if (category) params.set('category', category)
-  if (emailOnly) params.set('email_only', 'true')
+  if (emailFilter && emailFilter !== 'all') params.set('email_filter', emailFilter)
   if (columns && columns.length) {
     for (const c of columns) params.append('columns', c)
   }

