// The serverless backend for the contact form. // // A standalone `*.serverless.ts` — it is NOT part of a long-running api. The // CLI bundles + ships it on its own: // // voltro serverless dev functions/sendMessage.serverless.ts # run locally (:8910) // voltro serverless deploy --target node | cloudflare | scaleway # ship it // // It receives the form payload, validates it against `input`, and sends an // email via Resend's HTTP API using the framework-provided `HttpClient`. // Edge-safe: no `node:*`, so it also runs on a Cloudflare Worker. // // Secrets come from `ctx.env` (set at deploy time): RESEND_API_KEY is required; // CONTACT_TO / CONTACT_FROM are optional overrides. import { Effect, Schema } from 'effect' import { HttpClient, HttpClientRequest } from '@effect/platform' import { defineServerless, ServerlessHttpError } from '@voltro/serverless' const EMAIL_RE = /^[^@\s]+@[^@\s]+\.[^@\s]+$/ export default defineServerless({ name: 'send-message', method: 'POST', input: Schema.Struct({ name: Schema.String.pipe(Schema.minLength(1), Schema.maxLength(120)), email: Schema.String.pipe(Schema.pattern(EMAIL_RE)), message: Schema.String.pipe(Schema.minLength(1), Schema.maxLength(5000)), }), output: Schema.Struct({ ok: Schema.Boolean }), runtime: { memoryMb: 128, timeoutSeconds: 10 }, handler: ({ name, email, message }, ctx) => Effect.gen(function* () { const apiKey = ctx.env.RESEND_API_KEY const to = ctx.env.CONTACT_TO ?? 'you@example.com' const from = ctx.env.CONTACT_FROM ?? 'Contact form ' // No key wired yet → fail with a clear 503 the form can render. The // function never pretends to send: honest in dev, honest in prod. if (!apiKey) { return yield* Effect.fail( new ServerlessHttpError({ status: 503, message: 'email-not-configured', detail: 'Set RESEND_API_KEY (+ optionally CONTACT_TO / CONTACT_FROM) in the function env.', }), ) } const http = yield* HttpClient.HttpClient const request = HttpClientRequest.post('https://api.resend.com/emails').pipe( HttpClientRequest.setHeader('authorization', `Bearer ${apiKey}`), HttpClientRequest.bodyUnsafeJson({ from, to: [to], reply_to: email, subject: `New message from ${name}`, text: `${message}\n\n— ${name} <${email}>`, }), ) const response = yield* http.execute(request) if (response.status >= 400) { const detail = yield* response.text return yield* Effect.fail(new ServerlessHttpError({ status: 502, message: 'email-send-failed', detail })) } return { ok: true } }), })