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.

shell
npm install @avelto/sdk fastify

2. 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:

shell
export AVELTO_API_KEY=av_test_...
Sandbox rules

Test keys never deliver anything; they run the pipeline and record events. The sandbox sender you@sandbox.avelto.dev only delivers to your account's verified owner email and to the simulator addresses delivered@, bounced@ and complained@sandbox.avelto.dev. Anything else is refused with 403 sandbox_recipient_not_allowed. To send to anyone, verify a domain (step 5).

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.

TypeScript
// server.ts
import 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: "[email protected]",
      to: "[email protected]",
      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:

shell
curl -X POST http://localhost:3000/send

The 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
HTTP/1.1 201 Created
Content-Type: application/json

{ "id": "9c1f4a52-6f6e-4b8f-9b8e-2e1a5c7d3f10" }
Retries are built in

The SDK retries a request up to three times with exponential backoff and jitter: always on 429, 502 and 503, and on network errors and 504 too when repeating is safe (reads, deletes and sends, which carry an idempotency key). Every emails.send carries an Idempotency-Key (a random UUID unless you pass idempotencyKey), so a retried send never produces a second email. See Retries to tune or disable it.

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.

TypeScript
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),
  };
});
shell
curl http://localhost:3000/emails/9c1f4a52-6f6e-4b8f-9b8e-2e1a5c7d3f10

5. 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 (three DKIM CNAMEs, an SPF TXT and a DMARC TXT) at your DNS provider; it polls domains.get until status is verified. Use a subdomain such as mail.acme.com.

TypeScript
// verify-domain.ts
import { Avelto } from "@avelto/sdk";

const avelto = new Avelto(process.env.AVELTO_API_KEY!);

const domain = await avelto.domains.create({ name: "mail.acme.com" });

for (const r of domain.dns_records) {
  console.log(`${r.type}\t${r.name}\t${r.value}\t(${r.purpose})`);
}

// Publish the records, then poll. domains.get re-checks DNS on every call.
let status = domain.status;
while (status === "pending") {
  await new Promise((r) => setTimeout(r, 30_000));
  status = (await avelto.domains.get(domain.id)).status;
}
console.log(status); // "verified" or "failed"
shell
npx tsx verify-domain.ts

Once 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 [email protected]. 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.