Self-hostable contact form backend with a zero-dependency TypeScript SDK. Default email provider is Resend, SMTP is supported, and Railway deployment steps are included below.
This gets the API running locally and verifies it end-to-end.
- Node.js 20+
- pnpm 9+
corepack enable
corepack prepare pnpm@9.15.4 --activatepnpm installcp packages/server/.env.example packages/server/.envSet the minimum required variables in packages/server/.env:
Resend example:
EMAIL_PROVIDER=resend
RESEND_API_KEY=re_xxxxxxxxxxxxxxxxxxxxxxxx
MAIL_TO=you@example.com
MAIL_FROM=noreply@yourdomain.comSMTP (Mailhog/local) example:
EMAIL_PROVIDER=smtp
SMTP_HOST=localhost
SMTP_PORT=1025
SMTP_SECURE=false
MAIL_TO=you@example.com
MAIL_FROM=noreply@example.compnpm --filter @contactkit/server devBy default, the server starts at http://localhost:3000.
Health endpoint:
curl -s http://localhost:3000/healthContact endpoint (works as-is when TURNSTILE_SECRET is not set):
curl -s -X POST http://localhost:3000/contact \
-H 'Content-Type: application/json' \
-d '{
"name": "Jane",
"email": "jane@example.com",
"message": "Hello from curl"
}'Click the Deploy on Railway button at the top of this README — it provisions everything automatically.
- Create a new project in Railway and connect this repository.
- Add environment variables in Railway:
- Required:
MAIL_TO,MAIL_FROM - Provider-specific:
- Resend:
EMAIL_PROVIDER=resend,RESEND_API_KEY=... - SMTP:
EMAIL_PROVIDER=smtp,SMTP_HOST,SMTP_PORT, optionalSMTP_USER,SMTP_PASS,SMTP_SECURE
- Resend:
- Recommended:
ALLOWED_ORIGINS=https://your-frontend-domain.com
- Required:
- Deploy the service.
- Verify the deployment:
curl -s https://your-app.up.railway.app/health<script type="module">
import { ContactClient } from 'https://cdn.jsdelivr.net/npm/@contactkit/client/dist/index.js';
const client = new ContactClient({ baseUrl: 'https://contact.example.com' });
await client.send({
name: 'Jane',
email: 'jane@example.com',
message: 'Hello!',
subject: 'Inquiry', // optional
turnstileToken: '...', // optional
});
</script>import { ContactClient, ContactError } from '@contactkit/client';
const client = new ContactClient({
baseUrl: 'https://contact.example.com',
timeoutMs: 10_000, // optional, default 10 s
});
try {
const { id } = await client.send({
name: 'Jane',
email: 'jane@example.com',
message: 'Hello from Node!',
});
console.log('Sent, message id:', id);
} catch (err) {
if (err instanceof ContactError) {
console.error(err.code, err.status); // e.g. "validation" 400
}
}- Full env reference: packages/server/.env.example
- Required in all environments:
MAIL_TO,MAIL_FROM - Provider selection:
EMAIL_PROVIDER=resend(default) orEMAIL_PROVIDER=smtp - Resend requires:
RESEND_API_KEY - SMTP requires:
SMTP_HOST,SMTP_PORT(plus optional auth and TLS flags) - Security/ops knobs:
ALLOWED_ORIGINS,RATE_LIMIT_MAX,RATE_LIMIT_WINDOW, optionalTURNSTILE_SECRET
pnpm test
pnpm build
pnpm lintMIT © dimeloper