Sending email from Microsoft Dynamics CRM is most reliable when you treat CRM activity and email delivery as two separate jobs. Microsoft Dynamics 365 CRM, built on Dataverse, can notify an HTTPS endpoint when a row changes; a secure server-side relay can then validate that event, map its fields, and call Volanea’s email API.

This matters because Volanea does not provide a native Microsoft Dynamics CRM marketplace app or plug-in. Rather than pretending there is an install-and-connect flow, this guide shows the architecture that fits the platform: a Dataverse webhook registered for a specific event, plus a small middleware endpoint that owns the email credentials and sending logic.

The example below sends a welcome email when a new Lead row is created. The same pattern works for Contact, Account, Opportunity, Case, or a custom Dataverse table, provided that the fields you need are available in the event context.

What the integration looks like

The integration has three components:

  1. Dataverse / Dynamics 365 CRM detects a business event, such as creation of a Lead row.
  2. A webhook receiver you operate receives Dataverse’s RemoteExecutionContext payload, verifies it, extracts CRM fields, and applies business rules.
  3. Volanea accepts a REST API request from that server-side receiver and delivers the message through your configured sending domain.

That middle component is intentional, not needless plumbing. A Dynamics webhook delivers a Dataverse execution context, not a ready-made email payload. It also should not be given a long-lived email API key in a browser form, client-side script, or broadly visible CRM configuration record.

For a production deployment, host the receiver as an HTTPS endpoint in a service you control: an Azure Function, Azure App Service, AWS Lambda behind API Gateway, Cloud Run, or a conventional Node.js application. The endpoint should return quickly after accepting a valid request. If you need more resilience, put the normalized email job on a queue before calling the email provider.

The real Dynamics CRM trigger: a Dataverse Create message

Microsoft Dynamics CRM is now generally delivered as Dynamics 365 applications on Microsoft Dataverse. In Dataverse terms, the event used in this guide is the Create message for the lead table, registered at the PostOperation pipeline stage.

That wording is important. A “new lead” is not normally a generic form-submission webhook. It is a row being created in the lead table. A user can create it from the sales UI, an import, an API integration, or automation; all can result in the same Dataverse Create operation.

Register the webhook using the Dataverse Plug-in Registration Tool or another supported registration process. Configure a webhook service endpoint that points to your HTTPS receiver, then register a step with these values:

  • Message: Create
  • Primary Entity / table: lead
  • Pipeline stage: PostOperation
  • Execution mode: asynchronous is usually the safer choice for non-critical welcome or notification email
  • Endpoint: your middleware URL, such as https://crm-email.example.com/dataverse/lead-created

PostOperation is useful because the Lead has been created and has an identifier. It also makes it possible to use the Lead ID as a stable event identifier in your own records. Avoid making a normal marketing or welcome email part of the user’s synchronous record-save path. If the email provider is slow, unavailable, or rejects a message, sales users should not be blocked from creating a lead.

Why a Create trigger is not enough by itself

A Create step runs for every Lead creation. Your CRM may create leads without an email address, may create internal test leads, or may import older records that should not receive an immediate message. The receiver should therefore enforce eligibility rules rather than assuming every event should send.

Typical rules include:

  • Send only when emailaddress1 is present and syntactically usable.
  • Do not send where a custom consent field is false or absent.
  • Exclude internal domains and known test records.
  • Send only when a custom field such as new_sendwelcomeemail is true.
  • Use a template version or campaign marker so future automation changes do not resend old messages.

If the desired trigger is a sales process change instead, register the appropriate Dataverse operation that changes the relevant field and use a step on Update. For example, an Opportunity stage automation is commonly represented by a change to a business-process-flow-related field or a custom status field, not necessarily a simple human-readable “stage changed” event. Inspect the actual table and columns in your environment before registering the step.

What Dataverse posts to your webhook

A Dataverse webhook posts a serialized RemoteExecutionContext. It is an execution payload for plug-ins and service endpoints, containing metadata such as the message name, primary entity name, correlation ID, operation ID, and input parameters.

For a Lead Create, the Lead is normally available in InputParameters under the key Target. The entity attributes are serialized as key/value items. The precise set of attributes is not a promise that every Lead field will appear in every event: fields that were not supplied during creation may not be present, and lookup, option-set, money, and date values have different serialized representations.

A representative payload has this shape:

{
  "MessageName": "Create",
  "PrimaryEntityName": "lead",
  "PrimaryEntityId": "4ce7902f-8f93-ef11-8a69-000d3a32b128",
  "Stage": 40,
  "Mode": 1,
  "CorrelationId": "ddcb2f5e-5418-43b3-8c73-a4d1ae1ed260",
  "OperationId": "15b6a302-b6fd-4e8a-863e-0a2eb6b86af7",
  "InputParameters": [
    {
      "key": "Target",
      "value": {
        "__type": "Entity:http://schemas.datacontract.org/2004/07/Microsoft.Xrm.Sdk",
        "LogicalName": "lead",
        "Id": "4ce7902f-8f93-ef11-8a69-000d3a32b128",
        "Attributes": [
          { "key": "firstname", "value": "Avery" },
          { "key": "lastname", "value": "Ng" },
          { "key": "emailaddress1", "value": "avery@example.net" },
          { "key": "companyname", "value": "Northwind Studio" },
          { "key": "new_sendwelcomeemail", "value": true }
        ]
      }
    }
  ]
}

Treat this as an event envelope, not as an email body. Your receiver should confirm that it received the expected MessageName, table, and stage before acting. It should also cope with the fact that an attribute can be missing entirely. For example, an absent firstname is not the same thing as an empty string, and emailaddress1 may be unavailable on a particular Lead creation route.

Use CRM IDs, not names, for event identity

The Lead Id, CorrelationId, and OperationId are more useful operationally than a person’s name. Store at least the Lead ID and a chosen event key when you create a send job. That record lets you investigate a complaint, suppress a duplicate, or explain exactly why a message was sent.

Do not use the recipient email address alone as the idempotency key. One person can legitimately become a lead more than once, and a single lead can have more than one relevant automation over its lifetime.

Build the secure webhook relay

The receiver below is a complete Node.js/Express-style example of the mapping layer. It accepts the Dataverse context, turns the Attributes array into an object, checks the trigger and consent flag, writes a simple durable de-duplication record through an abstract alreadyProcessed function, then sends the resulting message to Volanea.

Set VOLANEA_API_KEY and VOLANEA_FROM as server-side environment secrets in the hosting platform. The exact credentials and request fields should be checked against the current email API reference and setup guides before deployment, especially if your account uses a different API version or sender format.

import express from "express";

const app = express();
app.use(express.json({ type: "application/json" }));

// Replace these with a database table, Redis SET NX, or a queue-backed job store.
const processed = new Set();
async function alreadyProcessed(eventKey) {
  if (processed.has(eventKey)) return true;
  processed.add(eventKey);
  return false;
}

function targetAttributes(context) {
  const target = context.InputParameters?.find((item) => item.key === "Target")?.value;
  if (!target || target.LogicalName !== "lead") return null;

  return Object.fromEntries(
    (target.Attributes || []).map(({ key, value }) => [key, value])
  );
}

app.post("/dataverse/lead-created", async (req, res) => {
  const context = req.body;

  // Validate the configured Dataverse webhook authentication at the edge or here.
  // Do not accept unauthenticated public POSTs in production.
  if (context.MessageName !== "Create" || context.PrimaryEntityName !== "lead") {
    return res.status(202).json({ ignored: "not a lead Create event" });
  }

  const attributes = targetAttributes(context);
  if (!attributes) return res.status(400).json({ error: "Lead Target missing" });

  const email = attributes.emailaddress1;
  const firstName = attributes.firstname || "there";
  const company = attributes.companyname || "your team";

  if (!attributes.new_sendwelcomeemail || typeof email !== "string" || !email.includes("@")) {
    return res.status(202).json({ ignored: "not eligible for welcome email" });
  }

  // A database uniqueness constraint is better than this in-memory example.
  const leadId = context.PrimaryEntityId || context.InputParameters
    ?.find((item) => item.key === "Target")?.value?.Id;
  const eventKey = `lead-welcome:${leadId}`;

  if (await alreadyProcessed(eventKey)) {
    return res.status(202).json({ ignored: "duplicate delivery" });
  }

  const volaneaResponse = await fetch("https://api.volanea.com/v1/emails", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.VOLANEA_API_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      from: process.env.VOLANEA_FROM,
      to: [email],
      subject: `Welcome to ${company}, ${firstName}`,
      html: `<p>Hi ${escapeHtml(firstName)},</p><p>Thanks for your interest in ${escapeHtml(company)}.</p>`,
      text: `Hi ${firstName},\n\nThanks for your interest in ${company}.`
    })
  });

  if (!volaneaResponse.ok) {
    const detail = await volaneaResponse.text();
    console.error("Volanea send failed", volaneaResponse.status, detail);
    // Return a non-2xx response only if your retry/idempotency design can safely handle it.
    return res.status(502).json({ error: "email provider rejected send" });
  }

  return res.status(202).json({ accepted: true, leadId });
});

function escapeHtml(value) {
  return String(value).replace(/[&<>'"]/g, (character) => ({
    "&": "&amp;", "<": "&lt;", ">": "&gt;", "'": "&#39;", '"': "&quot;"
  }[character]));
}

app.listen(process.env.PORT || 3000);

The field mapping is explicit: emailaddress1 becomes the Volanea recipient, firstname contributes to the greeting, and companyname contributes to the subject and body. It is better to keep that mapping in code or a version-controlled transformation than to bury it in an undocumented automation canvas.

Before copying the endpoint and payload into production, verify the currently supported Volanea send endpoint and message schema in your account documentation. API versions and optional fields can change; copying an endpoint from a blog without checking the reference is a common integration failure.

Keep the Volanea API key out of Dynamics client configuration

The Volanea API key belongs in the middleware host’s secrets facility, not in a Dynamics form script, browser-accessible environment variable, page source, Power Apps client component, or custom field on a Lead.

In the architecture above, Dynamics only knows the webhook receiver URL and its webhook authentication configuration. The receiver knows the Volanea API key through a server-side secret such as Azure Key Vault, an Azure Function application setting backed by Key Vault, AWS Secrets Manager, Google Secret Manager, or an equivalent managed secret store.

This separation has practical benefits:

  • CRM users and browser clients never receive an email-sending credential.
  • You can rotate the Volanea key without editing Lead forms or redeploying client JavaScript.
  • A compromised webhook token does not automatically expose the provider key.
  • The relay can restrict sender addresses, recipients, templates, and rate limits before an email request leaves your infrastructure.

Protect the inbound Dataverse-to-relay hop separately. Configure one of Dataverse’s supported webhook authentication choices when registering the service endpoint, and validate it at your gateway or receiver. Use HTTPS only. Put rate limits, request-size limits, and structured request logging in front of the application.

Never log the Volanea authorization header. Be careful with request-body logging too: webhook payloads can contain names, addresses, phone numbers, and other CRM data. Log a correlation ID, Lead ID, event key, decision result, provider response status, and a redacted recipient identifier instead.

Design the email before you automate it

A CRM trigger can make sending easy; it cannot make an unwanted email welcome. Decide what qualifies a Lead for the workflow before registering a broad Create step.

For a simple transactional acknowledgement, a custom Boolean field such as new_sendwelcomeemail can act as an explicit gate. For marketing content, you usually need consent, jurisdiction-specific policy, suppression handling, and a clear distinction between operational notices and promotional campaigns. A new Lead does not automatically mean marketing permission.

Sender identity and deliverability

Use a From address on a domain you control and authenticate that domain before enabling the workflow. Domain authentication aligns your sending identity with the infrastructure used to deliver the mail and helps receiving systems evaluate it.

Keep CRM-generated mail narrowly scoped at launch. A welcome message to a person who just requested information is easier to justify and diagnose than a large, automatic campaign triggered by an imported Lead list. Monitor bounces, complaints, delivery outcomes, and response patterns after rollout.

Avoid inserting unescaped CRM content into HTML. Company names, notes, and free-text custom fields can contain characters that break markup or, worse, create unsafe content. The code example escapes values before adding them to HTML; a production template system should do the same by default.

When this breaks: failures specific to the webhook hop

Every automation of this kind has two boundaries: Dataverse to your receiver, and your receiver to Volanea. Design for both rather than assuming a successful record save equals a successful email delivery.

Retries can create duplicate sends

Webhook and network delivery are not a guarantee of exactly-once processing. A timeout, connection interruption, or receiver error can leave Dataverse unsure whether your application processed the event. Retried delivery is therefore possible, while the first attempt may already have reached Volanea.

The safe response is durable idempotency in your middleware. Create a unique database record before sending, keyed to a business event such as lead-welcome:<lead-id> or, where appropriate, a Lead ID plus automation version. If insertion fails because the key already exists, return a successful acknowledgement and do not send again.

The in-memory Set in the code is only a teaching device. It loses state after a restart and does not work across multiple instances. Use a database uniqueness constraint, a queue with de-duplication support, or another persistent coordination mechanism.

Webhook timeouts should not include long email work

Your receiver should validate and acknowledge the webhook quickly. A slow template render, an external customer-data lookup, or a delayed provider request can push the response beyond the calling system’s tolerance and cause a retry.

A stronger production design is:

  1. Validate the Dataverse event and authenticate the caller.
  2. Atomically persist a normalized send job with its idempotency key.
  3. Return a successful response promptly.
  4. Let a worker process the job, call Volanea, and record the provider result.

This pattern isolates CRM availability from email-provider latency. It also gives you a place to apply bounded retries for transient provider errors without repeatedly invoking the Dataverse webhook.

Some fields will be missing

A Lead created through different paths may not contain every expected attribute. A manual sales entry could have emailaddress1; an imported record may not. A custom consent field may be absent in older environments, unavailable to the registering user, or not included in the input entity because it was not set during the create operation.

Do not silently substitute a fallback recipient, and do not turn an absent consent field into assumed consent. Record an ignored_missing_email, ignored_missing_consent, or similar decision with the Lead ID. Those records will quickly show whether the issue is user process, mapping, permissions, environment customization, or a plan/product limitation in the source system.

The wrong event can fire

A Create message triggers whenever a Lead row is created, not only when a human completes a particular web form. Imports, data migrations, integrations, and tests can all create rows. Add a source marker, consent gate, or dedicated custom action to distinguish your intended path.

For update-triggered messages, register only against the relevant update behavior and compare the old and new business state in your own logic. Otherwise, unrelated edits to a Lead can repeatedly trigger a stage-based or status-based message.

Test with realistic CRM events

Start in a non-production Dataverse environment and a non-production email sender or restricted recipient list. Create Leads through the same paths your users and integrations will use, rather than testing only with a hand-written webhook JSON file.

A useful test matrix includes:

  • A Lead with an email address, consent flag, first name, and company name.
  • A Lead without emailaddress1.
  • A Lead with an empty first name.
  • A Lead created through import or API, if those are in scope.
  • The same webhook event delivered twice.
  • A temporary Volanea API failure.
  • An intentionally slow receiver response.
  • Names and company values containing &, <, quotation marks, and non-ASCII characters.

For each test, correlate the Dataverse operation with your receiver log, queue job if used, Volanea response, and final delivery event. The Dataverse correlation and operation identifiers are especially valuable for tracing a single CRM action through your system.

Do not test deliverability by sending repeated messages to people who did not ask for them. Use owned inboxes, approved test recipients, or a dedicated test domain. Once the mechanics work, test sender authentication and content with the same sender domain and template structure you plan to use in production.

Alternatives to a custom webhook relay

The native webhook-plus-relay route is best when you need predictable code, strict secrets handling, custom business rules, or durable idempotency. It is not the only option, however.

Power Automate can be suitable for lower-volume, low-complexity workflows when your organization already governs its connectors and environments carefully. The key question remains the same: can the flow make a securely authenticated HTTP request, transform the Dataverse data you need, and store the Volanea credential in an approved connection or secret mechanism? Validate connector availability and licensing in your tenant rather than assuming all HTTP actions are included.

Zapier or Make can also act as middleware for supported Dynamics 365 and HTTP workflows. They can reduce custom code for simple automations, but they add another vendor boundary, execution limits, retry behavior, and a separate place to manage secrets. For a customer-facing transactional message where duplicate prevention matters, inspect their replay and error-handling controls before relying on a no-code scenario.

A custom Dataverse plug-in is another advanced option. A plug-in can execute server-side logic, but calling an external email API directly from the transaction path is usually less attractive than handing work to a webhook or queue. Keep user-facing CRM operations fast, and keep email delivery independently observable.

Operational checklist before launch

Before enabling the registered step in production, confirm the following:

  • The webhook is registered for the intended Dataverse message, table, stage, and environment.
  • The receiver accepts HTTPS requests and validates configured webhook authentication.
  • The Volanea API key is held only in a server-side secret store.
  • The sending domain and From address are configured and approved for your intended mail type.
  • Every automated message has a consent and eligibility decision where required.
  • The receiver has durable de-duplication keyed to the actual business event.
  • Failures have an alert path and a replay process that cannot create duplicates.
  • Logs retain correlation IDs and status information without exposing full personal data or credentials.
  • Your team knows who owns CRM configuration, middleware deployment, email templates, and suppression decisions.

This ownership split matters more than it may seem. A CRM administrator may correctly register a webhook but not own DNS authentication or email policy; an engineering team may operate the relay but not know the sales process that makes an event eligible. Write down the contract between those teams.

Conclusion

To send email from Microsoft Dynamics CRM with Volanea, use Dataverse’s real event model rather than looking for a nonexistent native app. Register a PostOperation Create step on the lead table, receive the RemoteExecutionContext at a secured HTTPS endpoint, map the Target attributes to a Volanea email request, and keep the provider key in the server-side relay.

The email request itself is the easy part. The production-quality parts are determining eligibility, protecting credentials, returning quickly, handling missing CRM fields, and making repeated webhook delivery harmless. Build those safeguards first, and the integration can remain reliable as your CRM processes and sending volume grow.

FAQ

Does Volanea have a native Microsoft Dynamics CRM plug-in?

No. This integration uses a Dataverse webhook and middleware that calls Volanea’s REST API. There is no marketplace installation flow to rely on.

What starts the email in this example?

A Dataverse Create message for the lead table starts the flow. The webhook step is registered at the PostOperation stage, and the middleware sends only if its eligibility checks pass.

Can I put the Volanea API key in a Dynamics form script?

No. Form scripts execute in a client context and can expose credentials to users or browsers. Keep the API key in a server-side secret store used by the webhook receiver.

How do I stop duplicate emails when Dynamics retries a webhook?

Persist an idempotency record before sending, using a unique event key such as the Lead ID plus automation name or version. If the same event arrives again, acknowledge it without creating another send.

Why is the Lead email field sometimes absent from the webhook payload?

Dataverse event payloads reflect attributes supplied for that operation. A Lead created by import, API, or another process may not include emailaddress1 or your custom fields. Treat missing fields as an explicit no-send condition and log the reason.