Email Clean is an app for validating email addresses, running deeper cleaning checks, and saving results for authenticated users. It fits teams that need to process email lists, check deliverability, and keep verification results tied to user accounts. Use this guide if you want to run the app locally or self-host it for internal use. If you only need the hosted application, use https://email-clean.grorapid.com/.

What the app includes

The Email Clean stack includes:
  • a frontend on http://localhost:3000
  • a backend API on http://localhost:3001
  • MongoDB on the internal Docker network
The backend API is exposed under /api/v1. In local development, Next.js rewrites forward requests from the frontend origin to the backend, so requests sent to http://localhost:3000/api/v1/* are routed to http://localhost:3001/api/v1/*. Because of that, the preferred local API base URL is http://localhost:3000/api/v1. In production, the public app URL is https://email-clean.grorapid.com/, and the API base URL is https://email-clean.grorapid.com/api/v1.

Before you start

Make sure you have:
  • Docker Desktop, or Docker Engine with Compose support
  • a prepared backend/.env file in the application repository
If you are creating the env file for the first time, start from backend/.env.example.

Prepare your environment

Set the backend env values so the containers can talk to each other correctly:
DB_HOST=mongodb
FRONTEND_DOMAIN=http://localhost:3000
CORS_ALLOWED_ORIGINS=http://localhost:3000
Run Docker Compose with --env-file backend/.env. Compose resolves values like ${DB}, ${DB_USER}, and ${DB_PASSWORD} before the containers start, so this flag keeps backend/.env as the single source of truth. For Docker runs, the frontend should target the backend through the Docker network:
BACKEND_API_ORIGIN=http://backend:3001
Compose injects that value for the frontend container, so you do not need to change local frontend env files just for Docker.

Install and run in development mode

Use development mode first if you want the full stack with hot reload.
docker compose --env-file backend/.env -f docker-compose.dev.yml up --build
Run in detached mode:
docker compose --env-file backend/.env -f docker-compose.dev.yml up -d --build
Stop the stack:
docker compose --env-file backend/.env -f docker-compose.dev.yml down

Run the production-style stack

Use this mode when you want a production-like local stack.
docker compose --env-file backend/.env up --build
Run in detached mode:
docker compose --env-file backend/.env up -d --build
Stop the stack:
docker compose --env-file backend/.env down
In this mode, the frontend is the public entrypoint on http://localhost:3000, and the backend stays on the internal Docker network.

Verify the installation

After the containers start, check these URLs:
  • frontend: http://localhost:3000
  • direct backend: http://localhost:3001
  • preferred API base path through the app: http://localhost:3000/api/v1
  • production app: https://email-clean.grorapid.com/
  • production API base path: https://email-clean.grorapid.com/api/v1
If you are testing from the browser or a frontend client, use the frontend URL with /api/v1. Next.js rewrites will pass that request through to the backend. You can also call the health endpoint to confirm the API is reachable:
curl http://localhost:3001/api/v1/health-ping
If you plan to test authenticated routes, create or sign in to a user account first through the auth endpoints.

Reverse proxy

If you want to expose the app on a public domain, place Nginx in front of the frontend on port 3000. Keep the proxy target on 127.0.0.1:3000, not 3001, so the Next.js app stays the public entrypoint and continues routing /api/v1 requests to the backend through rewrites.

How to add Nginx as a reverse proxy

  1. Install Nginx, Certbot, and the Certbot Nginx plugin on your Ubuntu server.
    sudo apt update
    sudo apt install nginx certbot python3-certbot-nginx -y
    
  2. Make sure your domain points to the server and that ports 80 and 443 are open in your firewall.
  3. Create an Nginx site file such as /etc/nginx/sites-available/email-clean.grorapid.com.
  4. Add an initial HTTP-only server block like this and update the server_name if you are using a different domain:
    server {
      listen 80;
      listen [::]:80;
      server_name email-clean.grorapid.com;
    
      location ^~ /.well-known/acme-challenge/ {
        root /var/www/html;
        allow all;
      }
    
      location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
    
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    
        proxy_connect_timeout 10s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
      }
    }
    
  5. Enable the site and reload Nginx. If the default site is enabled, remove it first so it does not conflict.
    sudo rm -f /etc/nginx/sites-enabled/default
    sudo ln -s /etc/nginx/sites-available/email-clean.grorapid.com /etc/nginx/sites-enabled/
    sudo nginx -t
    sudo systemctl reload nginx
    
  6. Request and install the TLS certificate with Certbot.
    sudo certbot --nginx -d email-clean.grorapid.com
    
  7. After Certbot succeeds, your final config will look similar to this:
    server {
      listen 80;
      listen [::]:80;
      server_name email-clean.grorapid.com;
    
      location ^~ /.well-known/acme-challenge/ {
        root /var/www/html;
        allow all;
      }
    
      location / {
        return 301 https://$host$request_uri;
      }
    }
    
    server {
      listen 443 ssl http2;
      listen [::]:443 ssl http2;
      server_name email-clean.grorapid.com;
    
      ssl_certificate /etc/letsencrypt/live/email-clean.grorapid.com/fullchain.pem;
      ssl_certificate_key /etc/letsencrypt/live/email-clean.grorapid.com/privkey.pem;
      include /etc/letsencrypt/options-ssl-nginx.conf;
      ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
    
      location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
    
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    
        proxy_connect_timeout 10s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
      }
    }
    
  8. Open https://email-clean.grorapid.com/ and confirm the app loads through Nginx and that requests to /api/v1 still work from the same origin.

Troubleshooting

If port 3000 or 3001 is already in use, free that port or update the host port mapping in the relevant Compose file. If you change dependencies in either app, rebuild the stack before testing again:
docker compose --env-file backend/.env up --build
docker compose --env-file backend/.env -f docker-compose.dev.yml up --build

Next steps

Once the app is running, move to the Email Clean API reference for authentication flows, endpoint details, request bodies, and response formats.