Send email from MongoDB Atlas when a database event—not a browser action—should be the source of truth. This guide uses an Atlas Database Trigger that fires after an insert event, calls an Atlas Function, and sends a transactional message through Volanea’s REST API.

There is no native Volanea listing, app, or marketplace plugin for MongoDB Atlas. The direct integration is still practical because Atlas Database Triggers invoke server-side Atlas Functions, and Functions include an outbound HTTP client. That means the sending path can remain inside Atlas: a document is inserted into a collection, the Trigger receives the MongoDB change event, the Function maps the document into an email payload, and Volanea accepts the message over HTTPS.

This is a good fit for events that are already durably represented in MongoDB: a user record created after a form submission, an order inserted after payment confirmation, a support-request document written by an API route, or an invitation record created by an administrator. It is not a substitute for putting email credentials in frontend code. The Volanea key belongs in Atlas secret storage and is read only at Function runtime.

The architecture: document insert to delivered email

The concrete Atlas event in this tutorial is a Database Trigger configured for the insert operation type on a welcome_requests collection. Each time your application inserts a qualifying document, Atlas passes the corresponding MongoDB change event to the linked Atlas Function.

The path looks like this:

  1. Your application writes a document to app.welcome_requests.
  2. A MongoDB Atlas Database Trigger observes the change-stream insert event.
  3. Atlas invokes the linked Function with the change event object.
  4. The Function validates fullDocument.email, maps document values into a Volanea request, and adds a stable Idempotency-Key.
  5. The Function sends POST https://api.volanea.com/v1/send with Bearer authentication.
  6. Volanea queues and processes the transactional email, subject to your verified sender domain, suppression rules, and account settings.

Atlas Database Triggers are specifically designed to run server-side logic in response to database changes, including inserts, updates, replacements, and deletes. They watch change streams, then call an Atlas Function with the event data. Atlas also documents that trigger events are processed at least once, which matters enormously for email: without idempotency, one logical welcome event can become two welcome emails.

Why use a separate collection for sendable events?

You can watch your users collection directly and send a message on every new user insert. For many applications, that is enough. A separate welcome_requests collection is often safer because it makes the intent explicit: inserting the document means “send this message.”

For example, a user may be created before their email address is verified, or your application may import old users in bulk. A direct trigger on users would require more filtering and more care around historical inserts. An event-style collection makes the sending condition reviewable and provides a place for the business event ID, recipient, language, and message-specific data.

A useful document shape is:

{
  "_id": {"$oid": "66c8f0f0a1b2c3d4e5f67890"},
  "type": "welcome_email",
  "email": "maya@example.com",
  "firstName": "Maya",
  "accountId": "acct_12345",
  "createdAt": {"$date": "2026-10-09T16:20:00.000Z"}
}

The email address, message type, and business identifier are all present before the Trigger starts. Do not rely on a client to call the email API after it writes this document; that makes the database update and the email request two separate client-side operations, and either can succeed without the other.

What MongoDB Atlas actually sends to the Function

Atlas does not post your document directly to Volanea. A Database Trigger passes an argument representing the MongoDB change event to its linked Function. For an insert, the fields you normally need are operationType, fullDocument, ns, and documentKey.

A representative insert event looks like this:

{
  _id: {
    _data: "8266C8F0F0000000012B022C0100296E5A1004..."
  },
  operationType: "insert",
  clusterTime: Timestamp(1724170480, 1),
  wallTime: ISODate("2026-10-09T16:20:00.000Z"),
  fullDocument: {
    _id: ObjectId("66c8f0f0a1b2c3d4e5f67890"),
    type: "welcome_email",
    email: "maya@example.com",
    firstName: "Maya",
    accountId: "acct_12345",
    createdAt: ISODate("2026-10-09T16:20:00.000Z")
  },
  ns: {
    db: "app",
    coll: "welcome_requests"
  },
  documentKey: {
    _id: ObjectId("66c8f0f0a1b2c3d4e5f67890")
  }
}

Treat fullDocument as your application data and documentKey._id as the durable identifier for the logical event. That identifier is the best source for an idempotency key because it stays the same if Atlas retries the same trigger event.

Do not build idempotency from the current time, a random UUID generated inside the Function, or the Function invocation ID. Those values change on a retry, which defeats duplicate protection.

Insert events are simpler than update events

For this first implementation, use insert only. With an insert event, the created document is available as fullDocument, which makes field mapping clear and predictable.

Update-based email rules can work too—for example, send a message when an order changes from pending to paid—but they require a stricter filter. You need to distinguish a meaningful stage transition from unrelated edits, and you should verify which event fields are available under your Trigger configuration. If a message depends on an exact before-and-after comparison, write an explicit event document in your application transaction or use a dedicated outbox-style collection rather than inferring business intent from every update.

Set up the Atlas Database Trigger

In MongoDB Atlas, go to Triggers under Streaming Data, open the linked App Services application, and create a Function first. Then create a Database Trigger that calls that Function.

Use these settings as the conceptual configuration:

  • Trigger type: Database Trigger
  • Database: app
  • Collection: welcome_requests
  • Operation type: Insert
  • Linked Function: sendWelcomeEmail
  • Event ordering: start with unordered processing unless your business process truly requires serial handling

A Database Trigger can be scoped to a collection, database, or deployment. Scope it to one collection for transactional email whenever possible. Collection-level scope reduces unrelated events, makes permissions and observability simpler, and lowers the chance that an internal write accidentally triggers a second workflow.

Add a match expression before writing code

If your collection stores more than one kind of message request, use the Trigger’s match expression to limit invocations. For example, this match expression allows only insert events for documents whose type is welcome_email:

{
  "operationType": "insert",
  "fullDocument.type": "welcome_email"
}

This is better than invoking the Function for every document and returning early in JavaScript. Atlas documents that Database Triggers support $match expressions to filter change events and $project expressions to limit event data. Filtering at the Trigger keeps unnecessary executions and external API calls out of your path.

Keep the Function’s validation anyway. A Trigger filter reduces noise; it is not a replacement for checking that an email address and any required fields are actually usable before you send.

Check cluster eligibility before depending on it

Database Triggers use MongoDB change streams. Atlas documents that Flex clusters and federated database instances do not support Database Triggers. In other words, this direct pattern is not a “standard-plan webhook” feature you can assume exists on every Atlas deployment.

If your cluster cannot run Database Triggers, use application middleware instead: have the server endpoint that inserts the document send the Volanea request after a successful write, or publish the event to a worker/queue that owns delivery. A no-code route through an automation platform can also be appropriate for low-volume operational workflows, but it introduces another credential store, execution quota, and retry system.

Store the Volanea API key as an Atlas Secret

Volanea’s sending endpoint uses a secret API key through the Authorization: Bearer ... header. Store that key in an Atlas Secret, then link it to a Value that the Function can read using context.values.get().

In the linked App Services application, go to Values under Build, choose Create a Value, and select the Secret type. A clear name is VOLANEA_API_KEY. Atlas Secrets are private values stored on the Atlas backend; after creation, Atlas does not let you directly read the saved secret value. That is exactly the property you want for a sending credential.

The Function accesses the secret like this:

const apiKey = context.values.get("VOLANEA_API_KEY");

Why the key must never be client-visible

A Volanea secret key authorizes sending. If it is exposed in browser JavaScript, a mobile bundle, a public environment variable, a screenshot, or a repository, an attacker can use it to send mail as your account until you revoke it.

Do not put it in:

  • a React, Vue, Next.js public environment variable, or any value prefixed for browser exposure;
  • a MongoDB document that clients can read;
  • a form submission payload;
  • a Trigger source file committed with the secret literal;
  • a frontend request that calls Volanea directly.

The Atlas Function is server-side code, so it is an appropriate boundary for the secret. Keep access tight: only administrators who manage the App Services application should be able to modify Values or Function code. Rotate the key when staff changes, after a suspected exposure, and whenever your credential-management policy requires it.

Working Atlas Function: map an insert event to Volanea

The Function below expects the welcome_requests document shape shown earlier. It maps fullDocument.email to Volanea’s to array, uses a verified address for from, creates HTML and text alternatives, and uses the document _id as the stable idempotency component.

Create an Atlas Function named sendWelcomeEmail and use this source:

exports = async function(changeEvent) {
  // This Function is intended for a Database Trigger filtered to inserts.
  if (changeEvent.operationType !== "insert") {
    return { skipped: true, reason: "not_an_insert" };
  }

  const event = changeEvent.fullDocument || {};
  const recipient = String(event.email || "").trim().toLowerCase();
  const firstName = String(event.firstName || "there").trim();
  const eventId = String(changeEvent.documentKey && changeEvent.documentKey._id);

  if (!recipient || !recipient.includes("@")) {
    console.error("welcome email not sent: missing or invalid email", {
      eventId,
      hasEmail: Boolean(event.email)
    });
    return { skipped: true, reason: "missing_or_invalid_email" };
  }

  if (!eventId || eventId === "undefined") {
    throw new Error("Cannot build idempotency key: missing documentKey._id");
  }

  const apiKey = context.values.get("VOLANEA_API_KEY");
  if (!apiKey) {
    throw new Error("VOLANEA_API_KEY is not configured as an Atlas Secret/Value");
  }

  // Replace this with an address on a domain verified in Volanea.
  const from = "Acme <hello@mail.example.com>";
  const subject = "Welcome to Acme";

  const text = [
    `Hi ${firstName},`,
    "",
    "Welcome to Acme. Your account is ready.",
    "",
    "If you did not create this account, contact support."
  ].join("\n");

  // Escape data that originated outside trusted application code before
  // putting it into HTML. This example escapes only the display name.
  const safeName = firstName
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;");

  const html = [
    `<p>Hi ${safeName},</p>`,
    "<p>Welcome to Acme. Your account is ready.</p>",
    "<p>If you did not create this account, contact support.</p>"
  ].join("");

  const payload = {
    from,
    to: [recipient],
    subject,
    text,
    html
  };

  const response = await context.http.post({
    url: "https://api.volanea.com/v1/send",
    headers: {
      "Authorization": [`Bearer ${apiKey}`],
      "Content-Type": ["application/json"],
      // Stable across an Atlas retry of this same inserted document.
      "Idempotency-Key": [`mongodb-welcome:${eventId}`]
    },
    body: JSON.stringify(payload)
  });

  const responseText = response.body ? response.body.text() : "";

  if (response.statusCode < 200 || response.statusCode >= 300) {
    console.error("Volanea send failed", {
      eventId,
      statusCode: response.statusCode,
      responseText
    });
    throw new Error(`Volanea send failed with HTTP ${response.statusCode}`);
  }

  console.log("Volanea send accepted", {
    eventId,
    statusCode: response.statusCode,
    recipient
  });

  return {
    sent: true,
    eventId,
    statusCode: response.statusCode
  };
};

The important field mapping is explicit:

MongoDB Atlas change eventVolanea send request
changeEvent.fullDocument.emailto: [recipient]
constant verified senderfrom
application message rulesubject
changeEvent.fullDocument.firstNameinterpolated into text and html
changeEvent.documentKey._idIdempotency-Key header

Volanea’s REST endpoint for a single transactional message is POST /v1/send; it uses Bearer authentication and accepts an Idempotency-Key header for safe retries. Send both text and html: the text part remains useful for plain-text clients, assistive workflows, and recipients whose clients do not render HTML.

For the broader request schema, response behavior, and sender-domain setup, consult the Volanea email API reference.

Verify your sender domain before production

The from value in the Function is not a cosmetic detail. It must use an address on a domain you have configured and verified in Volanea. Start with a sender such as Acme <hello@mail.example.com>, then complete the DNS authentication records Volanea provides for your sending domain.

Do not use the recipient’s domain in the From address. A message to maya@example.com should not claim to be from anything@example.com unless that domain is yours and verified. Misaligned sender identity creates deliverability, authentication, and abuse problems.

Test with a controlled document

Before connecting a form or a production user-signup path, insert one test document into the watched collection. Confirm four things:

  1. The Trigger execution appears as successful in Atlas logs.
  2. The Function logged a 2xx response from Volanea.
  3. The email appears in Volanea’s send activity.
  4. The receiving mailbox gets the message with the expected From address, subject, and content.

Then repeat the test by retrying the same event deliberately, rather than inserting a new document. The same event ID and the same Idempotency-Key should not create a second logical send. A new document has a new _id, so it should legitimately create a new message.

Make the event durable before the email call

A Database Trigger is a strong starting point, but no network request can make your database write and an external email submission one atomic transaction. The database insert may succeed and the email call may fail, or Volanea may accept the request but the Atlas Function may lose the response before it records success.

The practical answer is to model the inserted document as an outbox event. The insert is the durable intent to send. The Trigger tries to submit it, and idempotency makes a repeat submission safe.

For higher-value workflows, add fields that describe the lifecycle:

{
  "type": "welcome_email",
  "email": "maya@example.com",
  "firstName": "Maya",
  "status": "pending",
  "attemptCount": 0,
  "createdAt": "2026-10-09T16:20:00.000Z"
}

Be careful if the Trigger updates the same collection after sending. A collection-level Trigger watching all operations can react to its own status update and create recursion. Either filter strictly to insert, use a separate status collection, or write only fields that your match expression excludes. Atlas explicitly warns that broad database- or deployment-level trigger configurations can create recursive Trigger activity.

Do not confuse submission with inbox placement

A successful Volanea API response means the provider accepted the send request; it does not mean the recipient has read the message or that every mailbox will place it in the inbox. Bounces, complaints, suppressions, recipient-server responses, and mailbox-provider filtering happen later.

That distinction affects product logic. Do not mark an account “email verified” because you submitted a welcome message. Use a verified link, one-time token, or other explicit confirmation mechanism. Do not unlock an irreversible operation merely because a delivery request was accepted.

When this breaks: Atlas-to-API failure modes

The hop from Atlas Trigger to Atlas Function to Volanea is short, but it has specific failure modes. Plan for them before the first production incident.

Trigger retries can cause duplicate sends

Atlas processes Trigger events at least once. An execution may be retried after a transient failure, and a failure can happen after Volanea has already accepted the message but before the Function sees or records a successful response.

The protection is the Idempotency-Key header. Keep it deterministic and tied to the durable business event, such as mongodb-welcome:<document-id>. Do not add a random suffix. If the trigger replays the same insert, Volanea receives the same key and can treat it as the same send rather than a new email.

Outbound HTTP timeouts leave an ambiguous result

A timeout is not proof that the email was not accepted. It means Atlas did not get a complete response in time. The remote service may have received the request and started processing it before the connection failed.

This is why retrying with the same idempotency key is safe and retrying with a new one is dangerous. Log the event ID, recipient domain, status code when available, and non-sensitive response details. Never log the Volanea secret key or a full message body containing unnecessary personal information.

Missing payload fields should skip, not improvise

Your MongoDB documents are application data, so fields can be absent, blank, malformed, or created by a legacy code path. The sample Function skips a document without a usable email value and throws when it cannot build an idempotency key.

Do not quietly send to a fallback address in production. That turns a data-quality bug into a privacy incident. Instead, record a structured error, alert on repeated failures, and repair the source document or application validation.

Also separate missing fields from unavailable infrastructure. Atlas documents that Database Triggers cannot be defined on Flex clusters or federated database instances because those deployments do not support change streams. In that case, there is no Trigger payload to repair: use a server-side application route or worker as the sending boundary.

A verified domain mismatch fails before mail delivery

If the from address does not belong to a verified Volanea sender domain, the request cannot become a valid production message. Keep the sender address as a server-controlled constant or a carefully validated allowlist—not as a value copied from a user document.

Trigger backlog can turn “instant” mail into delayed mail

Database Triggers process events when capacity is available. An unordered Trigger can process many events concurrently, but actual throughput depends on event volume and Function runtime. Large payloads, slow external calls, and excessive logging all add work.

Keep event documents small, minimize outbound calls per Trigger invocation, and avoid generating a full marketing digest for every insert. For a large batch, queue individual transactional events or have a controlled worker use Volanea’s batch-oriented workflow where appropriate.

Security and content rules for this integration

Treat an email-generating document as a security-sensitive input. Data stored in MongoDB may originally have come from a form, an integration, a CSV import, or an administrator. It still needs validation before it reaches an email body.

Escape content inserted into HTML

The Function example escapes firstName before inserting it into HTML. Preserve that pattern for every user-controlled text value. Escaping matters even when recipients are your own users: unescaped content can produce malformed messages, misleading links, or injected markup.

For complex messages, prefer a reviewed template with a defined variable set rather than assembling large HTML strings in a Trigger Function. The database event should carry data, not arbitrary markup. For example, store firstName, planName, and activationUrl; do not store a raw HTML body submitted by an untrusted client.

Validate recipient ownership where it matters

A Database Trigger validates that a document exists, not that the recipient intended to receive a message. For account emails, ensure your application already has the right consent or transactional basis. For invitations, verify that the inviter is authorized and that the destination address is what your product intends to contact.

If an address arrives from a public workflow, validate format in your application and consider checking address quality before committing high-value sequences. Volanea provides a free email address verification tool for a quick pre-send check, but verification should complement—not replace—your own consent, suppression, and abuse controls.

Alternatives when a direct Atlas Trigger is not the right fit

The direct Database Trigger and Atlas Function route is usually the lowest-latency option because it does not require a separate application server merely to relay a JSON request. But it is not the only valid design.

Send from your application backend

If your Node.js, Python, Java, Go, or other backend already owns user signup and order creation, it can call Volanea after a successful database write. This is often easier to test with your existing observability and deployment pipeline.

Use the same principles: durable event identifier, idempotency key, server-only secret, validation, and a way to retry failures. The tradeoff is that the database is no longer the autonomous event source; if another service writes directly to MongoDB, it can bypass the email action.

Use a queue or worker for high-volume workflows

For large volumes or workflows requiring backoff, routing, and detailed dead-letter handling, write an event to MongoDB and have a worker consume it. The worker can rate-limit by tenant, prioritize password resets over low-priority notifications, and retry with controlled exponential backoff.

This introduces operating complexity but can be worth it for revenue-impacting or regulated messaging. The Volanea request itself remains the same: POST /v1/send, Bearer authentication, and a stable idempotency key based on the durable event.

Use automation middleware for operational workflows

If you cannot use Atlas Database Triggers on your deployment, or the workflow is an internal low-volume automation, a middleware platform can receive an event and send an HTTP request to Volanea. Keep in mind that this creates a second system that must hold the secret, map fields, handle retries, and be monitored.

For product email such as passwords, receipts, login links, account alerts, and customer onboarding, direct server-side code or a managed worker usually gives you clearer control over authentication, failure handling, and auditability.

Production checklist

Before enabling this integration for real recipients, verify the following:

  • The Atlas deployment supports Database Triggers and the watched collection is on a linked cluster.
  • The Trigger filters to the exact event you intend to send, such as operationType: "insert" and fullDocument.type: "welcome_email".
  • The Volanea API key is stored as an Atlas Secret/Value, not in code or a client application.
  • The from address uses a domain verified in Volanea.
  • The Function validates required fields and does not send to fallback recipients.
  • HTML interpolates escaped user-controlled values, and a meaningful plain-text part is included.
  • Every logical event produces a deterministic Idempotency-Key.
  • Atlas Function logs contain event identifiers and statuses but not secrets or unnecessary personal data.
  • You have tested both a successful insert and a replay/retry scenario.
  • Your alerting covers failed Trigger executions and a sustained rise in skipped malformed events.

The central idea is simple: write a durable event, let Atlas react to that event, and make the external email request repeat-safe. Once those three pieces are in place, sending email from MongoDB Atlas is not a fragile client-side side effect—it is a controlled server-side workflow.

FAQ

Can MongoDB Atlas send email directly?

MongoDB Atlas does not send SMTP email directly as a database feature. It can run a Database Trigger that invokes an Atlas Function, and that Function can make an outbound HTTPS request to Volanea’s email API.

Does Volanea have a MongoDB Atlas marketplace integration?

No. This setup does not use a native Atlas marketplace app or plugin. It uses Atlas Database Triggers, Atlas Functions, an Atlas Secret, and Volanea’s REST endpoint.

What should the MongoDB Atlas Trigger watch?

For a welcome-message workflow, watch insert events on a dedicated collection such as welcome_requests. Use a match expression such as operationType: "insert" plus fullDocument.type: "welcome_email" to avoid firing for unrelated documents.

How do I prevent duplicate emails when Atlas retries a Trigger?

Set Volanea’s Idempotency-Key header to a stable value based on the inserted document’s _id or another immutable business event ID. Reuse that exact value for every retry of the same logical event.

Can I use this on an Atlas Flex cluster?

No. Atlas documents that Flex clusters do not support Database Triggers because they do not support change streams. Use your application backend, a queue worker, or middleware to call Volanea instead.