Skip to the console
Ping250

Documentation

Ping250 docs

Everything the tool does, how the SMTP flow works under the hood, and how to script it.

How a test works, end to end

Every action is a single HTTP POST from your browser to a server-side route. Credentials live only in that request body. The route validates the input with Zod, runs the same-origin check, the per-IP rate limiter and the bot check, then builds a throwaway Nodemailer transport, does the work, closes the transport, and returns a structured result. Nothing is written to a log, a database, or disk.

browser ──POST /api/smtp/send──▶ route handler
   │                                 │  1. Zod validation
   │   { config, message,            │  2. same-origin (CSRF) check
   │     turnstileToken }            │  3. per-IP rate limit (Upstash)
   │                                 │  4. Cloudflare Turnstile verify
   │                                 │  5. createTransport(config)
   │                                 │  6. transporter.verify()
   │                                 │  7. transporter.sendMail(message)
   │◀── { ok, latencyMs, … } ────────┘  8. transport.close()

Ports and TLS, the part everyone gets wrong

Most failed tests are a port/TLS mismatch. The secure flag must match the port: implicit TLS on 465, STARTTLS on 587.

PortModeNotes
465Implicit TLSsecure = true. The connection is encrypted from the first byte. Preferred when offered.
587STARTTLSsecure = false. Starts plain, then upgrades to TLS via STARTTLS. The modern submission standard.
25Usually plainServer-to-server relay. Blocked for outbound by most clouds and ISPs - avoid for testing.

Error glossary

Failures are mapped from Nodemailer's machine codes (EAUTH, ECONNECTION, ETIMEDOUT, EENVELOPE…) and the server's SMTP reply into a plain title, an explanation, and a next step.

  • Authentication failed

    The server rejected the username/password (SMTP 535), or wants auth before talking (530).

    Fix: Check the credentials. Gmail/Zoho/Fastmail need an app password. SendGrid's username is the literal string “apikey”.

  • Connection timed out

    No response within the timeout (ETIMEDOUT).

    Fix: Confirm the port and TLS mode line up, and that no firewall blocks egress. Port 25 is commonly blocked.

  • Host not found

    The hostname did not resolve in DNS (ENOTFOUND).

    Fix: Fix the host spelling - smtp.gmail.com, not smtp.gmail.co.

  • TLS handshake failed

    The encrypted session could not be set up (EPROTOCOL / certificate errors).

    Fix: Match port to TLS mode (465 = secure on, 587 = secure off). Self-signed certs on internal servers need explicit trust.

  • Recipient address rejected

    The server refused the To (or From) address (550 / 5.1.1).

    Fix: Send to an address you control. Some servers require From to equal the authenticated account.

  • Rejected by server policy

    Accepted the connection, refused the message (5.7.x) - spam, relay or reputation.

    Fix: Check SPF/DKIM/DMARC with the diagnostics tab. New IPs are often throttled.

SPF, DKIM and DMARC in one paragraph each

SPF lists which servers may send for your domain. A record ending in -all (hard fail) is strongest; ~all (soft fail) is common; +all authorises the entire internet and is a hole.

DKIM signs each message so receivers can prove it was not altered in transit. It lives at selector._domainkey.domain; the diagnostics tab tries the common selectors.

DMARC tells receivers what to do when SPF or DKIM fails - p=none only reports, p=quarantine sends to spam, p=reject refuses outright.

HTTP API

The same logic the UI uses is exposed as JSON routes. A failed SMTP action returns HTTP 200 with ok: false and a structured error; HTTP 4xx is reserved for validation, rate-limit and bot-check faults.

POST /api/smtp/verify
{ "config": { "host", "port", "secure", "username", "password", "fromName", "fromEmail" },
  "turnstileToken": "…" }

POST /api/smtp/send
{ "config": { … },
  "message": { "to", "subject", "text"?, "html"?, "attachment"? },
  "turnstileToken": "…" }

POST /api/diagnostics
{ "domain": "acme.dev", "turnstileToken": "…" }

With Turnstile enabled in production, programmatic callers must supply a valid token. For local scripting, set NEXT_PUBLIC_TURNSTILE_ENABLED=false and the routes are gated only by the rate limiter. A dedicated, separately-authenticated API mode for CI/CD is on the roadmap.

Security and abuse prevention

This tool relays real mail through user-supplied credentials, so it is treated as a potential open-relay vector. In v1: every field is Zod-validated; recipients are capped at one per request; sends are gated by Cloudflare Turnstile plus a per-IP sliding-window rate limit (Upstash in production, in-memory for local dev); and a same-origin check blocks cross-site request forgery from browsers. Credentials are never logged, stored, or persisted server-side.

Saved profiles are encrypted in the browser with AES-256-GCM under a key derived from your passphrase via PBKDF2-SHA256 (210,000 iterations). Only ciphertext, salt and IV are stored in localStorage; the passphrase never leaves the page.