Send email from Amplitude without pretending there is a native marketplace app: use Amplitude’s Webhook destination to forward selected product events to a secure server endpoint, then have that endpoint call Volanea’s email API. This pattern keeps email credentials off the client, gives you control over eligibility and copy, and protects recipients from duplicates when Amplitude retries a webhook.

Amplitude does support outbound HTTP for event streaming. Its Webhook: Events · User Properties destination can forward selected events and user data to a URL you control; it is available on Amplitude Plus, Growth, and Enterprise plans. The integration is event-driven: when Amplitude ingests an event that matches your selected filters, it forwards the payload to your endpoint. It is not a campaign scheduler, an email composer, or a native Volanea plugin. (amplitude.com)

This guide builds the production-safe version of that flow:

  1. Your application tracks a meaningful event in Amplitude, such as trial_started.
  2. Amplitude streams that event to a webhook endpoint you operate.
  3. The endpoint validates the request, extracts the recipient and event context, and decides whether the event should generate an email.
  4. The endpoint calls POST /v1/send at Volanea with a stable Idempotency-Key.
  5. Volanea queues and sends the transactional message through your verified sender domain.

The result is an integration that is explicit about its trigger, reliable under retries, and flexible enough to support welcome emails, trial confirmations, receipts, onboarding nudges, security notices, and other event-driven transactional messages.

What actually triggers an email in Amplitude

The concrete trigger is an event ingested by Amplitude.

For this example, the event is named trial_started. Your product sends it when the backend successfully creates a trial, not when a visitor merely opens the trial form. That distinction matters. If the email confirms a successful business action, the analytics event should represent the completed action too.

A server-side event could look conceptually like this before it reaches Amplitude:

{
  "user_id": "usr_01JQ8P4A7V",
  "event_type": "trial_started",
  "time": 1760000000000,
  "event_properties": {
    "plan": "pro",
    "trial_days": 14,
    "workspace_name": "Acme Studio",
    "trial_id": "trial_01JQ8P"
  },
  "user_properties": {
    "email": "maya@example.com",
    "first_name": "Maya"
  },
  "insert_id": "evt_01JQ8P4A7V"
}

When Amplitude ingests that event, the Webhook destination can forward it if trial_started is included in the destination’s event filter. In the Amplitude UI, create the destination under Amplitude Data → Catalog → Destinations, select Webhook: Events · User Properties, create the sync, then enable Send Events. Amplitude’s docs describe this as automatic forwarding when events are ingested, rather than a scheduled or on-demand delivery mechanism. (amplitude.com)

This is an important architectural constraint: Amplitude can be the source of the trigger, but it is not the place to build a multi-step email workflow with arbitrary business logic. Treat it as a stream producer. Your relay is the decision point between behavioral data and an external side effect: sending a message.

Choose events that are safe to turn into messages

Not every analytics event deserves an email. Product telemetry can be noisy, repeated, delayed, anonymous, or generated from automated tests. A page_viewed event is almost never an email trigger. A completed payment, confirmed trial, verified account, or requested export can be.

Good event-trigger candidates typically have all of these properties:

  • A durable business meaning. invoice_paid is clearer than checkout_step_3.
  • A known recipient. The user has an email address and the message is appropriate for that address.
  • A clear send policy. The event should result in one message, or in a carefully controlled series.
  • A server-side source where possible. This avoids client retries, browser extensions, and spoofed event properties becoming email triggers.
  • A stable event identifier. An insert_id, order ID, trial ID, or equivalent lets you deduplicate sends.

For a trial_started confirmation, one successfully created trial should mean one email. That becomes the basis for the idempotency design later in this guide.

The Amplitude Webhook route, not a native app

There is no native Volanea app, marketplace installation, or one-click Amplitude destination for this integration. The supported route is Amplitude Webhook event streaming to an endpoint you own.

Amplitude’s Webhook destination accepts a webhook URL and lets you configure up to five additional request headers beyond its preset Content-Type: application/json and User-Agent: Amplitude/Webhook/1.0 headers. That makes it practical to send a shared secret to your relay for basic request authentication. (amplitude.com)

Your destination URL should be an HTTPS endpoint such as:

https://events.example.com/webhooks/amplitude

Do not point Amplitude directly at the Volanea send endpoint. Even though a webhook destination can send custom headers, direct routing creates several problems:

  • Amplitude’s event payload is not the same as Volanea’s send-message payload.
  • You need to validate recipient eligibility and required fields before calling an email provider.
  • You need a stable idempotency key derived from the event.
  • You should avoid placing a high-privilege email API key in a third-party destination configuration when a narrowly scoped shared webhook secret can protect the inbound hop instead.
  • You may need to suppress emails for internal users, test projects, unsubscribed users, or events that lack a verified recipient address.

A relay can be a serverless function, an API route in your application, a container service, or a worker runtime. Its job is intentionally small: authenticate, validate, deduplicate, map, enqueue or send, and return a successful response promptly.

What Amplitude sends

With the default event payload, Amplitude forwards an event in its event format. The useful fields for this integration are normally event_type, user_id, event_properties, user_properties, time, and insert_id. The exact fields present depend on how you instrument Amplitude. For example, if your implementation identifies users with device_id instead of user_id, a forwarded payload may not include user_id. (amplitude.com)

A representative payload received by your relay for the event above is:

{
  "user_id": "usr_01JQ8P4A7V",
  "event_type": "trial_started",
  "time": 1760000000000,
  "event_properties": {
    "plan": "pro",
    "trial_days": 14,
    "workspace_name": "Acme Studio",
    "trial_id": "trial_01JQ8P"
  },
  "user_properties": {
    "email": "maya@example.com",
    "first_name": "Maya"
  },
  "insert_id": "evt_01JQ8P4A7V"
}

Treat this as a schema you own, not a universal guarantee. In Amplitude event streaming, additional event and user properties need to be selected deliberately; absence of a property should be expected and handled. Amplitude also notes that forwarded user-property values are strings for most streaming destinations, so parse or validate values such as trial_days, booleans, and timestamps rather than assuming their original type. (amplitude.com)

Define the contract before enabling the destination

Write down the minimum event contract in the same repository as your relay. For the example integration, the contract is:

FieldSourceRequiredPurpose
event_typeAmplitude eventYesEnsures only trial_started is eligible
insert_idAmplitude eventStrongly recommendedStable deduplication key
event_properties.trial_idEvent propertyRecommendedBusiness-level deduplication fallback
user_properties.emailUser propertyYesRecipient email address
user_properties.first_nameUser propertyNoPersonalization
event_properties.planEvent propertyNoEmail content
event_properties.trial_daysEvent propertyNoEmail content

If email is not reliably available as a user property, do not guess from an identifier. Either include a trusted recipient property in the originating event or have the relay look up the user from your application database using user_id. The latter is often preferable for sensitive messages because your application database is the system of record for email address, account state, consent, and internal-user status.

Configure the Amplitude Webhook destination

Set up the destination only after the endpoint is deployed and can accept test traffic.

  1. In Amplitude Data, open Catalog and then Destinations.
  2. Search for and select Webhook: Events · User Properties.
  3. Create a sync and enter your HTTPS relay URL.
  4. Add an extra request header such as X-Amplitude-Relay-Secret with a long random secret value.
  5. Turn on Send Events.
  6. Filter the sync to the exact event or events that are allowed to produce email, such as trial_started.
  7. Select the extra event and user properties your relay contract needs.
  8. Save the sync, test it with a non-production event, and inspect your relay logs before enabling production traffic.

Use a secret that is unique to this destination. Do not reuse a password, a database token, or the Volanea API key. The relay should compare the incoming header against its own server-side environment variable using a timing-safe comparison where your runtime makes that practical.

Amplitude does not publish one fixed source IP address for webhook forwarding, so an IP allowlist alone is not a dependable control. Validate the shared secret, require HTTPS, rate-limit the endpoint, and keep its behavior narrow. (amplitude.com)

Keep the Volanea key out of Amplitude and out of the browser

The Volanea secret key belongs in the relay’s server-side secret store, not in a browser configuration file and not in an Amplitude client SDK initialization value.

For example:

VOLANEA_API_KEY=sk_...
AMPLITUDE_RELAY_SECRET=a-long-random-value
EMAIL_FROM=Acme <updates@mail.example.com>

The Amplitude side stores only the relay URL and the separate inbound webhook secret in the destination’s extra-header configuration. The browser receives neither secret. Your Amplitude project API key may be embedded in client instrumentation because it identifies the analytics project; it is not a substitute for protecting an email-sending credential.

A Volanea secret can send email. If it lands in client-visible JavaScript, a mobile app bundle, a public repository, a browser extension capture, or an exposed network request, an attacker could use it to send mail from your account. Keep it in a server environment variable or managed secret service, restrict access to the runtime that needs it, and rotate it if exposure is suspected.

For current endpoint options, authentication behavior, and API examples, consult the Volanea email API reference and setup guides. Volanea’s send endpoint is POST /v1/send, accepts a secret key, and supports Idempotency-Key for safe retries. (volanea.com)

Map the Amplitude payload to a Volanea email

The relay needs to perform an explicit translation. Amplitude describes what happened in your product. Volanea needs a recipient, a verified sender, a subject, and message content.

For the trial_started example, the mapping is:

Amplitude fieldVolanea field or useExample
user_properties.emailto[0]maya@example.com
configured server variablefromAcme <updates@mail.example.com>
user_properties.first_nameHTML and text personalizationMaya
event_properties.planHTML and text personalizationpro
event_properties.trial_daysHTML and text personalization14
event_properties.workspace_nameHTML and text personalizationAcme Studio
insert_id or trial_idIdempotency-Keyamplitude:trial_started:evt_...

The sender must use a domain that has been verified for your Volanea account. Do not use an arbitrary from address assembled from the Amplitude event; sender identity is configuration, not user input.

Working Node.js relay example

The following example uses an Express-style handler. It validates the inbound secret, accepts only trial_started, requires an email address, escapes untrusted content before it enters HTML, and creates a stable idempotency key before calling Volanea.

import crypto from "node:crypto";
import express from "express";

const app = express();
app.use(express.json({ limit: "256kb" }));

function timingSafeEqual(a, b) {
  const left = Buffer.from(a || "");
  const right = Buffer.from(b || "");
  return left.length === right.length && crypto.timingSafeEqual(left, right);
}

function escapeHtml(value) {
  return String(value ?? "")
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#039;");
}

function isEmail(value) {
  return typeof value === "string" && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
}

app.post("/webhooks/amplitude", async (req, res) => {
  const receivedSecret = req.get("X-Amplitude-Relay-Secret");

  if (!timingSafeEqual(receivedSecret, process.env.AMPLITUDE_RELAY_SECRET)) {
    return res.status(401).json({ error: "unauthorized" });
  }

  const event = req.body;

  // The concrete Amplitude trigger for this integration.
  if (event.event_type !== "trial_started") {
    return res.status(204).end();
  }

  const props = event.event_properties || {};
  const user = event.user_properties || {};
  const recipient = user.email;

  if (!isEmail(recipient)) {
    // Returning 204 avoids retrying an event that cannot ever produce an email.
    console.warn("Amplitude event missing valid email", {
      eventType: event.event_type,
      userId: event.user_id,
      insertId: event.insert_id
    });
    return res.status(204).end();
  }

  const firstName = escapeHtml(user.first_name || "there");
  const workspace = escapeHtml(props.workspace_name || "your workspace");
  const plan = escapeHtml(props.plan || "selected");
  const trialDays = Number.parseInt(props.trial_days, 10) || 14;

  // Prefer insert_id. Fall back to a durable business ID if supplied.
  const sourceId = event.insert_id || props.trial_id;
  if (!sourceId) {
    console.error("No stable event ID; refusing non-idempotent email send");
    return res.status(422).json({ error: "missing insert_id or trial_id" });
  }

  const idempotencyKey = `amplitude:trial_started:${sourceId}`;

  const volaneaResponse = await fetch("https://api.volanea.com/v1/send", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.VOLANEA_API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": idempotencyKey
    },
    body: JSON.stringify({
      from: process.env.EMAIL_FROM,
      to: [recipient],
      subject: `Your ${trialDays}-day ${plan} trial is ready`,
      html: `
        <p>Hi ${firstName},</p>
        <p>Your <strong>${trialDays}-day ${plan} trial</strong> for
        <strong>${workspace}</strong> has started.</p>
        <p>You can now sign in and invite your team.</p>
      `,
      text: `Hi ${user.first_name || "there"},\n\nYour ${trialDays}-day ${props.plan || "selected"} trial for ${props.workspace_name || "your workspace"} has started.\n\nYou can now sign in and invite your team.`
    })
  });

  const responseText = await volaneaResponse.text();

  if (!volaneaResponse.ok) {
    console.error("Volanea send failed", {
      status: volaneaResponse.status,
      sourceId,
      responseText
    });

    // A 5xx response can be retried by Amplitude. Idempotency prevents a second send
    // if Volanea accepted the first attempt but the relay did not receive its response.
    if (volaneaResponse.status >= 500 || volaneaResponse.status === 429) {
      return res.status(503).json({ error: "temporary email provider failure" });
    }

    // A permanent mapping or validation problem should be investigated, not retried forever.
    return res.status(204).end();
  }

  console.info("Volanea email accepted", {
    eventType: event.event_type,
    sourceId,
    recipient
  });

  return res.status(200).json({ ok: true });
});

app.listen(3000);

The request includes both HTML and plain-text content. That is a sound default for transactional mail: recipients whose clients do not render HTML still receive a comprehensible message. Volanea accepts one recipient or up to 50 recipients in a single send request, but one event should generally map to one recipient for this kind of confirmation. (volanea.com)

Use idempotency because webhook delivery is at least once

The most important reliability detail in this integration is that a webhook retry does not mean the first attempt definitely failed.

Amplitude retries failed event-streaming deliveries with exponential backoff. Its retry pipeline can attempt delivery up to 10 times over a four-hour window. Your endpoint could successfully call Volanea but lose the response due to a timeout, process restart, or network interruption; Amplitude would then retry the same event. (amplitude.com)

Without deduplication, the customer gets multiple trial confirmation emails.

Use the exact same Idempotency-Key for every attempt to send the message associated with the same business event. In the example, the key is:

amplitude:trial_started:<insert_id>

If your event ingestion always provides a unique insert_id, it is a good transport-level identifier. If your product has a durable business object such as trial_id, invoice_id, or password_reset_request_id, include or use it too. The best choice is the identifier that represents the actual one-time side effect.

For an invoice receipt, use something like:

amplitude:invoice_paid:invoice_01JQ8X

For an onboarding message that must be sent once per account rather than once per event:

amplitude:workspace_created:workspace_01JQ8X

Idempotency is not a replacement for event design. If the product emits two legitimately distinct trial_started events for the same trial because of an instrumentation defect, two different insert_id values will look like two different sends. Add business-level checks in your own datastore when the desired rule is “once per trial,” “once per order,” or “once per user per day.”

When this breaks: Amplitude-to-email failure modes

This integration has two hops: Amplitude to your relay, then your relay to Volanea. Diagnose failures according to the hop where they occur.

Amplitude retries create duplicate-send risk

Amplitude retries when the webhook endpoint does not return a successful response. A retry can occur after your relay has already performed the email send but before Amplitude receives the success response.

Fix: derive Idempotency-Key from insert_id, trial_id, or another immutable business ID. Do not generate a random UUID inside the relay for each request; that makes every retry appear new.

Also return 200 OK only after you have reached a durable point. For a simple low-volume relay, that can be after Volanea accepts the idempotent send request. For higher volume, write the event ID to a queue or database transactionally, return success, and let a worker perform the send.

Webhook timeouts lead to retries and backlogs

Amplitude targets a p95 end-to-end event-streaming latency of 60 seconds, but your receiver should not consume that budget doing template rendering, database fan-out, or slow downstream API calls. Amplitude specifically recommends an asynchronous pattern for webhook processing that returns success quickly when handling may take longer. (amplitude.com)

Fix: keep the HTTP handler narrow. Validate the secret, validate the schema, record the durable job, and return 200. A worker can then call Volanea, retry transient failures, and log the provider response. If you use the synchronous example above, keep the request path fast and instrument latency carefully.

Required payload fields are missing

A common problem is expecting user_properties.email to arrive automatically. Amplitude only forwards properties you choose in the destination setup, and user identity fields differ based on your implementation. A mobile-first anonymous flow may have a device_id without a user_id; an identify call may update properties later than the triggering event.

Fix: select the fields explicitly in the destination configuration and test with a real event. Make the relay reject or safely no-op missing recipients instead of sending to an empty value or attempting to infer an address. If recipient accuracy matters more than convenience, use user_id to retrieve the email from your application database.

The Webhook destination itself is available only on Amplitude Plus, Growth, and Enterprise plans. If your plan does not include it, do not build around a direct Amplitude-to-relay path that your account cannot enable. Use a separate automation or middleware product that can receive the relevant event from a supported source, or send the transactional email from the application backend at the time the business action occurs. (amplitude.com)

A malformed event property breaks the email body

Event properties are analytics data. They may contain unexpected values, empty strings, numbers sent as strings, or user-controlled text. Rendering those values directly into HTML can produce broken markup or unsafe content.

Fix: validate types, set defaults, and HTML-escape values before insertion. Treat from, recipients, reply-to addresses, and template selection as controlled server-side configuration. Do not let an event property choose an arbitrary sender or raw HTML body.

The email provider rejects the request

A 400-level Volanea response usually indicates a permanent request issue: missing required fields, invalid recipient formatting, an unverified sender, or another validation failure. Retrying the same invalid payload simply creates noise.

Fix: log the response status and a redacted error body, alert on repeated validation failures, and return a non-retry response to Amplitude after recording the event for review. For temporary 429 or 5xx failures, return a retryable status from the relay or queue a durable job for controlled retry.

Event volume causes unexpected sends

Turning on Send Users or forwarding broad event filters can result in far more traffic than expected. Amplitude notes that Identify events can be sent when selected user properties change and count against event-streaming volume. (amplitude.com)

Fix: begin with a single exact event name, a non-production project, and a test recipient allowlist. Add counters for received events, skipped events, accepted sends, permanent errors, transient errors, and idempotency replays. Review them before adding more triggers.

Test the complete flow before production

A successful destination test only proves that Amplitude can reach an HTTP endpoint. It does not prove your customer receives a correct email. Test each layer separately.

1. Test the relay locally or in staging

Send the representative Amplitude JSON payload directly to the endpoint with a test secret. Confirm that the handler:

  • rejects a missing or incorrect X-Amplitude-Relay-Secret;
  • ignores an unrelated event_type;
  • declines an event without a usable recipient;
  • creates the expected idempotency key;
  • escapes event-derived HTML values;
  • logs no raw Volanea key or unnecessary personal data.

2. Test the Volanea request with a controlled inbox

Use a verified sender address and an internal mailbox. Confirm the sender name, From address, subject, HTML rendering, text alternative, and all mapped values. Make sure a malformed or missing workspace_name does not create an embarrassing message such as “Welcome to undefined.”

If address quality is a concern before sending high-value messages, validate the address in your signup flow or use the email address verification tool before it enters your user profile. Verification is not a substitute for consent, but it can catch obvious address problems early.

3. Test duplicate delivery deliberately

Post the same webhook JSON twice with the same insert_id. Your relay will make two requests, but both must use the same Idempotency-Key. Verify that the outcome is one message, not two.

Then simulate an upstream timeout. Have the relay call Volanea successfully but delay its response long enough for the caller to consider it failed. Replay the same event and confirm that the idempotency behavior remains safe.

4. Test missing-field behavior

Try each of the following payload variations:

  • no user_properties.email;
  • no insert_id but a valid trial_id;
  • neither an insert_id nor a trial_id;
  • trial_days as a string;
  • HTML characters in workspace_name;
  • an anonymous event with only device_id.

Document the expected response for each case. Operationally, a safe no-op with a useful log is usually better than a flawed email or an endless retry loop.

Alternatives and when not to use Amplitude as the trigger

Amplitude Webhooks work well when the event is already a trusted, identified, server-side analytics event and behavioral analytics is the system that should decide which events flow downstream.

They are less ideal when the email is essential to a user action. Password resets, login alerts, payment receipts, account-verification links, and legal notifications should usually originate directly from your application backend or identity system. The same code path that commits the business action should enqueue the message. Analytics can still observe the action, but it should not be the only route to an email a user depends on.

For marketing-style lifecycle messages, it may be better to export a cohort rather than send on every raw event. Amplitude also offers Cohort Webhooks, which send add/remove batches for cohort membership and require a paid plan. Those are useful when the trigger is “this user entered the high-intent cohort,” but they are batch-oriented and need an asynchronous receiver that can acknowledge quickly. (amplitude.com)

Use this event-streaming approach when you need immediate, one-to-one transactional communication. Use cohort sync when membership in a behavioral audience is the meaningful trigger. Use your application backend when the message is part of a critical transaction.

Operational checklist for a production integration

Before enabling the destination for real users, verify the following:

  • The selected Amplitude event represents a completed, email-worthy action.
  • The event originates server-side or has adequate trust controls.
  • The Webhook destination filters only approved event names.
  • Required user and event properties are selected in Amplitude.
  • The relay is HTTPS-only and validates a unique shared secret.
  • VOLANEA_API_KEY exists only in server-side secret storage.
  • The From address belongs to a verified sending domain.
  • Every send uses a deterministic Idempotency-Key.
  • The relay distinguishes permanent validation failures from temporary provider failures.
  • HTML is escaped and all user-controlled values are validated.
  • Logs redact email content and secrets appropriately.
  • Monitoring covers webhook failures, duplicate attempts, provider errors, and send volume.
  • A staging destination and test inbox are available for changes.

The key idea is simple: Amplitude decides that an event happened; your relay decides whether an email should be sent; Volanea handles the email send. Keeping those responsibilities separate makes the system easier to secure, observe, and change.

FAQ

Can Amplitude send email directly through Volanea?

Not as a native Amplitude app or marketplace integration. Use Amplitude’s Webhook destination to send event data to a server endpoint you control, then call Volanea from that endpoint.

What Amplitude event should trigger the email?

Use an event that represents a completed business action, such as trial_started, invoice_paid, or report_requested. Filter the Webhook destination to that exact event name rather than forwarding all events.

Where should the Volanea API key be stored?

Store it only in the relay’s server-side environment or managed secret store. Do not place it in browser JavaScript, mobile configuration, Amplitude client configuration, or a public repository. The Amplitude destination should contain only the relay URL and a separate shared inbound secret.

How do I prevent duplicate emails when Amplitude retries?

Use a deterministic Volanea Idempotency-Key, derived from Amplitude insert_id or a durable business ID such as trial_id or invoice_id. Reuse that exact key for every retry of the same event.

What if the email address is missing from the Amplitude webhook?

Do not send. Return a safe non-retry response after logging the missing-field condition, or look up the email using a trusted user_id in your application database. Also verify that email is selected among the user properties forwarded by the destination.