Send email from Fastify
Five steps: install the SDK, create a test key, send to the sandbox from a POST route, read the log, then verify a domain so you can send to anyone.
1. Install
The SDK needs Node 20 or later and has no runtime dependencies. The scripts below use top-level await, so set "type": "module" in your package.json if it is not already.
npm install @avelto/sdk fastify2. Create an API key
Sign in, open API keys in the dashboard and create a test key. It starts with av_test_. Export it so the server can read it:
export AVELTO_API_KEY=av_test_...3. Send your first email
A POST route that calls the SDK and passes the API's error envelope through when a send is rejected. Other errors are rethrown so Fastify's error handler sees them.
// server.tsimport Fastify from "fastify";import { Avelto, AveltoError } from "@avelto/sdk";const app = Fastify();const avelto = new Avelto(process.env.AVELTO_API_KEY!);app.post("/send", async (_req, reply) => { try { const { id } = await avelto.emails.send({ from: "you@sandbox.avelto.dev", to: "delivered@sandbox.avelto.dev", subject: "Hello from Avelto", text: "It works.", }); return reply.code(201).send({ id }); } catch (err) { if (err instanceof AveltoError) { return reply .code(err.status || 502) .send({ error: { code: err.code, message: err.message } }); } throw err; }});await app.listen({ port: 3000 });Start the server with npx tsx server.ts and call the route:
curl -X POST http://localhost:3000/sendThe API answers 201 Created with the email id, and the route returns the same. Anything else is an AveltoError, and the route passes its status, code and message back as the same error envelope.
HTTP/1.1 201 CreatedContent-Type: application/json{ "id": "9c1f4a52-6f6e-4b8f-9b8e-2e1a5c7d3f10" }4. Check the log
Add a GET route that fetches the email by id. status moves from queued to sent to delivered, and events records each step: email.queued, email.sent, email.delivered.
app.get<{ Params: { id: string } }>("/emails/:id", async (req) => { const email = await avelto.emails.get(req.params.id); return { status: email.status, // "queued", then "sent", then "delivered" events: email.events.map((e) => e.type), };});curl http://localhost:3000/emails/9c1f4a52-6f6e-4b8f-9b8e-2e1a5c7d3f105. Verify a domain
Adding a domain is a one-off task, so run it as a script rather than a route. Add the DNS records it prints (an ownership TXT, an SPF TXT and a DMARC TXT, then three DKIM CNAMEs once the ownership record is found) at your DNS provider; it polls domains.get until status is verified. Use a subdomain such as mail.acme.com.
// verify-domain.tsimport { Avelto } from "@avelto/sdk";const avelto = new Avelto(process.env.AVELTO_API_KEY!);const domain = await avelto.domains.create({ name: "mail.acme.com" });const print = (d: typeof domain) => { for (const r of d.dns_records) { console.log(`${r.type}\t${r.name}\t${r.value}\t(${r.purpose})`); }};print(domain); // the ownership record first, with SPF and DMARC// Publish the records, then poll. domains.get re-checks DNS on every call.// Once the ownership record is found, the DKIM records appear: publish those too.let current = domain;while (current.status === "pending") { await new Promise((r) => setTimeout(r, 30_000)); const next = await avelto.domains.get(domain.id); if (next.ownership_verified && !current.ownership_verified) print(next); current = next;}console.log(current.status); // "verified" or "failed"npx tsx verify-domain.tsOnce the domain is verified, switch AVELTO_API_KEY to a live key (av_live_) and change from in the route to an address on it, such as hello@mail.acme.com. Nothing else changes.
Next
- Send email: every field, attachments, tags, scheduling and idempotency.
- Webhooks: get events pushed to your app.
- Test mode: test keys, the sandbox sender and the simulator addresses.