A minimal, secure form relay that forwards HTML form submissions to email (SMTP) or Telegram. Point your
form action at https://your-service/v1/recipient — use an email address or a Telegram chat ID as the recipient. No
backend logic needed on your site.
User personal data is not stored. The service does not use databases, files, or caches. The recipient is specified only in the form URL; for Telegram, the user obtains their chat ID via a bot and enters it into the form. No registration or mapping is required—maximum privacy.
Problem: Static sites and simple frontends have no backend to send contact/form data to email or messengers. You don't want to expose SMTP credentials in the browser or use a heavy backend.
Solution: This service exposes a single endpoint POST /v1/:recipient. You set your form’s action to
https://your-relay.example.com/v1/recipient@example.com (email) or https://your-relay.example.com/v1/123456789 (
Telegram chat ID). On submit, the relay validates the recipient, parses the form body (urlencoded or multipart), and
sends via your SMTP or Telegram bot. Rate limiting, request size limits, and security headers included.
Landing page: The root URL (/) serves a static site from the public/ folder. The contact form can send messages
through the relay once you enter your email or Telegram chat ID in the relay link field.
One command runs the service with SMTP (MailHog) and optional Telegram:
docker-compose up --build- Copy env and set at least SMTP vars (Compose overrides
SMTP_HOST/SMTP_PORTtomailhog:1025):cp .env.example .env # For MailHog: SMTP_HOST=mailhog, SMTP_PORT=1025, SMTP_FROM=noreply@localhost # Optional: TELEGRAM_BOT_TOKEN=your_bot_token for Telegram delivery
- Run
npm installonce to generatepackage-lock.json(thennpm ciin CI/Docker). - Start:
docker compose up -d --build. - Test email:
curl -X POST http://localhost:3000/v1/your@email.com -d "name=Test&message=Hello" - MailHog UI: http://localhost:8025
- Requirements: Node.js 18+
- Install, build, configure:
npm ci npm run build cp .env.example .env # Edit .env: SMTP_* and optionally TELEGRAM_BOT_TOKEN - Run:
npm start(dev:npm run dev).
| Variable | Required | Description |
|---|---|---|
PORT |
No | Server port (default: 3000) |
NODE_ENV |
No | development | test | production |
SMTP_HOST |
Yes | SMTP server hostname |
SMTP_PORT |
No | SMTP port (default: 587) |
SMTP_SECURE |
No | true for 465, false for 587/25 (default: false) |
SMTP_USER |
No | Auth username (empty for no auth) |
SMTP_PASS |
No | Auth password |
SMTP_FROM |
Yes | Sender address for outgoing mail |
MAX_FILES |
No | Max number of files in multipart request (default: 5) |
MAX_FIELDS |
No | Max number of non-file fields in multipart request (default: 100) |
MAX_PARTS |
No | Max total multipart parts (default: 120) |
TELEGRAM_BOT_TOKEN |
No | Bot token from BotFather; if set, Telegram delivery to numeric chat IDs is enabled |
RATE_LIMIT_WINDOW_MS |
No | Rate limit window in ms (default: 60000) |
RATE_LIMIT_MAX |
No | Max requests per window per IP (default: 10) |
BODY_LIMIT |
No | Max request body size in bytes (default: 10MB) |
Sends the request body to the given recipient.
- Parameter:
recipient— either a valid email address (for SMTP) or a Telegram chat ID (numeric string, 1–32 digits). Invalid format returns 400. - Request: supports common content types:
application/jsonapplication/x-www-form-urlencodedmultipart/form-datatext/plain
- Response:
200 OK— bodyOKwhen the message was sent.400 Bad Request— invalid or missing recipient in path.429 Too Many Requests— rate limit exceeded.501 Not Implemented— recipient is Telegram chat ID butTELEGRAM_BOT_TOKENis not set.502 Bad Gateway— Telegram delivery failed (e.g. chat not found, bot blocked by user).500 Internal Server Error— SMTP or server error.
- Health endpoint for uptime probes.
- Response:
200 {"ok": true}.
Example (email):
<form method="POST" action="https://your-relay.example.com/v1/contact@yourdomain.com">
<input name="name" placeholder="Name"/>
<input type="email" name="email" placeholder="Email"/>
<textarea name="message"></textarea>
<button type="submit">Send</button>
</form>curl -X POST https://your-relay.example.com/v1/you@example.com \
-d "name=Jane&email=jane@example.com&message=Hello"Example (JSON):
curl -X POST https://your-relay.example.com/v1/you@example.com \
-H "Content-Type: application/json" \
-d '{"name":"Jane","email":"jane@example.com","message":"Hello from JSON"}'Example (plain text):
curl -X POST https://your-relay.example.com/v1/you@example.com \
-H "Content-Type: text/plain" \
--data "Hello from plain text payload"Delivery to Telegram uses your bot and the recipient’s chat ID in the URL. No database, no username mapping, no registration — the user gets their ID from Telegram and uses it in the form.
- Create a bot: @BotFather → /newbot → copy the token.
- Configure: Add to your environment:
TELEGRAM_BOT_TOKEN=your_bot_token_here
- Start the service (e.g.
docker-compose up --buildornpm start). - Get chat ID: Start the bot once in Telegram and query:
Then use
https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getUpdates
message.chat.idas numeric recipient value. - Use in forms: Put that chat ID in the form
actionURL:
Limitation: The bot can send messages only to chats that have interacted with the bot at least once. No data about users or chat IDs is stored on the server.
- Typecheck:
npm run typecheck - All tests:
npm test - Unit only:
npm run test:unit - Integration only:
npm run test:integration - E2E (with MailHog): Set
SMTP_HOST=localhost, start MailHog (e.g.docker compose up -d mailhog), thennpm run test:e2e
Coverage is reported in coverage/ and enforced (e.g. ≥80% in Jest config).
- Build:
docker build -t smtp-form-relay . - Run: Pass env via file or
-e:docker run -p 3000:3000 --env-file .env smtp-form-relay
- Compose:
docker compose up --buildruns the app and MailHog; addTELEGRAM_BOT_TOKENto env for Telegram. One command for SMTP + optional Telegram.
The image runs as non-root user node (see Dockerfile).
- Render / Heroku: Use Docker or buildpack; set env vars (
SMTP_*,SMTP_FROM, optionalTELEGRAM_BOT_TOKEN). Use HTTPS in production. - VPS: Run with Node or Docker; put behind nginx/Caddy with HTTPS.
- Rate limiting per IP (configurable).
- Request size limit (default 10 MB).
- Helmet for security headers.
- Recipient validation (email format or numeric Telegram chat ID; no path traversal).
- Input sanitization for message body (newlines/control chars).
- No storage of form data or user identifiers.
MIT. See LICENSE.
- Human-readable release history is tracked in CHANGELOG.md.
- Project process for keeping changelog up to date lives in docs/CHANGELOG_PROCESS.md.
