TL;DR
Sending email from a serverless function with the Elastic Email SDK is the same call you'd make on Node, with one exception. On edge runtimes (Cloudflare Workers, Vercel Edge, Netlify Edge, Deno Deploy, Supabase Edge Functions) you pass a fetch-based axios instance as the third argument to EmailsApi, because those runtimes have no node:http. The working code for 12 platforms lives in one MIT-licensed folder, and every project exposes the same three endpoints.
Why does the SDK fail on edge runtimes?
@elasticemail/elasticemail-client-ts-axios is built on axios, and axios reaches for Node's http adapter unless told otherwise. Cloudflare Workers, Vercel Edge, Netlify Edge, Deno Deploy, and Supabase Edge Functions don't ship node:http, so the first call fails at bundle time or at runtime, depending on your bundler. On Node runtimes (AWS Lambda, Railway, Encore, Vercel's Node functions, Firebase, Azure Functions, Cloud Run, Convex Node actions) the default adapter is fine.
The fix: one argument
import axios from "axios";
import { Configuration, EmailsApi } from "@elasticemail/elasticemail-client-ts-axios";
const config = new Configuration({ apiKey: process.env.ELASTICEMAIL_API_KEY });
// Only needed on runtimes without node:http
const emailsApi = new EmailsApi(config, undefined, axios.create({ adapter: "fetch" }));
const { data } = await emailsApi.emailsTransactionalPost({
Recipients: { To: [to] },
Content: {
From: process.env.EMAIL_FROM!, // must be on a domain verified in Elastic Email
Subject: subject,
Body: [{ ContentType: "HTML", Content: `<p>${message}</p>` }],
},
});
// data.TransactionID, data.MessageID
Keep your top-level axios dependency on the same range the SDK uses (
~1.18.0) so npm dedupes it. Two copies of axios in the tree are the second most common reason a serverless send fails.
Deploy to Cloudflare Workers
cd cloudflare-workers
npm install
wrangler login
wrangler secret put ELASTICEMAIL_API_KEY
wrangler secret put ELASTICEMAIL_WEBHOOK_TOKEN
npm run deploy
On Workers, environment variables are not globals. They arrive as the second argument of
fetch(request, env), so the example buildsEmailsApiper request.nodejs_compatis enabled inwrangler.tomlso axios' Node imports resolve at bundle time.
Test it
curl $URL/health
curl -X POST $URL/send \
-H "Content-Type: application/json" \
-d '{"to":"you@yourdomain.com","subject":"Hello","message":"Hi from the edge!"}'
Register the webhook after you deploy.
When you save a webhook, Elastic Email sends one test event to the URL and only saves the webhook if it gets a 2xx, so deploy first. Requests are not signed; the examples append a shared token to the callback URL and verify it with a constant-time comparison. Events arrive as GET requests with the details in the query string (status, to, transaction, messageid).
The other 11 platforms:
| Platform | Runtime | The one thing to know |
|---|---|---|
| Vercel Functions | Node and Edge |
api/send.ts runs on Node, api/send-edge.ts on Edge; files starting with _ are not deployed |
| Supabase Edge Functions | Deno | No npm install, functions import npm: specifiers; set verify_jwt = false for the webhook; an auth-hook function delivers Supabase Auth emails through Elastic Email |
| AWS Lambda (SAM) | Node | The key is a NoEcho CloudFormation parameter; move it to Secrets Manager or SSM for production |
| Deno Deploy | Deno | deno task deploy |
| Netlify Functions | Node | netlify env:import .env |
| Railway | Node | A long-running Hono server, not a function; binds 0.0.0.0:$PORT
|
| Encore.ts | Node | Secrets are read with secret("ElasticEmailApiKey")(), not process.env
|
| Firebase Cloud Functions | Node |
defineSecret + .value(); a callable sendEmailToMe for Flutter and mobile apps so the API key never ships in the app |
| Azure Functions | Node v4 model |
/send requires the function key; the webhook stays anonymous behind ?token=
|
| Google Cloud Run | Node in a container | The runtime service account needs roles/secretmanager.secretAccessor on both secrets |
| Convex | Convex runtime + Node | The SDK call lives in a "use node" internal action that convex/http.ts invokes with ctx.runAction
|
FAQ
Do I need a verified domain to test?
Yes. The From address must be on a domain verified in Elastic Email; publishing the SPF and DKIM records takes a few minutes.
Is there a free tier?
Yes. 100 emails a day, no credit card. Every example in the repository runs on it.
Can I use SMTP instead of the SDK?
Yes. Point your tool at smtp.elasticemail.com with SMTP credentials (Settings > SMTP in the dashboard, not the API key).
The repo has a separate folder with 28 SMTP integrations, from Supabase Auth to WordPress to Grafana.
Repository: github.com/ElasticEmail/elasticemail-examples
Something missing? Issues and pull requests are open, and so are the comments below.
](https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv0ndneyraz7xo6miv8ki.png)
Top comments (0)