Send email from nopCommerce reliably by subscribing to nopCommerce’s server-side order events and calling Volanea from a custom plugin. nopCommerce does not provide a standard outbound-webhook automation screen for this job, and Volanea does not ship a native nopCommerce marketplace plugin, so the dependable integration point is nopCommerce’s built-in event system.
This guide implements an order-confirmation-style message, triggered when nopCommerce publishes OrderPlacedEvent. The same architecture can support shipment notices, payment follow-ups, B2B approval notifications, vendor alerts, or custom customer lifecycle messages. The important distinction is that nopCommerce sends an in-process C# event object—not a browser webhook payload—and your plugin maps that object into an authenticated Volanea POST /v1/send request.
The right integration model for nopCommerce
nopCommerce is an ASP.NET Core ecommerce application with an extension model built around plugins, services, scheduled tasks, and events. Its documented event system lets code subscribe to events that are raised when something happens in the store. For orders, the concrete trigger used here is OrderPlacedEvent, which exposes the newly created Order through its Order property.
That means the normal integration is not:
- Install a Volanea app from a nopCommerce marketplace.
- Configure an outbound webhook in a no-code automation builder.
- Paste an API key into storefront JavaScript.
None of those is the supported direct path described here. Instead, build and deploy a small server-side nopCommerce plugin, register an IConsumer<OrderPlacedEvent>, and send the message from the plugin process. This keeps the email action close to the business event that created the order and keeps the Volanea secret out of the customer-facing storefront.
Why an event consumer is better than editing nopCommerce core
A plugin gives the email adapter its own deployment boundary. You can change the subject line, change the sending domain, add a template, add an audit table, or disable the feature without modifying nopCommerce core files. That matters when upgrading nopCommerce because core edits are easy to overwrite or forget.
nopCommerce’s event documentation shows the intended pattern: publish an event with IEventPublisher, then implement the generic IConsumer<TEvent> interface to respond. OrderPlacedEvent is an example of that model. Your consumer receives the event, reads only the order data required for the message, and delegates external HTTP work to a dedicated sender service.
Why not call the email API from browser code
The checkout page and account pages run in a customer-controlled environment. Any API key placed in JavaScript, page markup, a downloadable configuration file, or a public endpoint response can be copied and misused to send mail from your account.
A Volanea secret key belongs only in server-side configuration. The nopCommerce web process should read it at runtime from an environment variable, a managed secret store, or deployment-level configuration that is not exposed to visitors. The customer’s browser should never receive the key, even indirectly.
The concrete nopCommerce trigger: OrderPlacedEvent
For this guide, the email begins when nopCommerce publishes OrderPlacedEvent. This is an order-placement event, not an OrderStatus field update and not a generic webhook named “order created.” The event object contains an Order object:
public class OrderPlacedEvent
{
public OrderPlacedEvent(Order order)
{
Order = order;
}
public Order Order { get; }
}
A consumer receives the event by implementing IConsumer<OrderPlacedEvent> and its HandleEventAsync method. This is the native hook you can rely on in a plugin.
What nopCommerce actually sends
There is no standard outbound HTTP JSON body sent by nopCommerce for OrderPlacedEvent. It is an in-process .NET event. The payload shape available to your code is therefore the event object and its associated database-backed domain object:
public async Task HandleEventAsync(OrderPlacedEvent eventMessage)
{
var order = eventMessage.Order;
// order.Id
// order.OrderGuid
// order.OrderTotal
// order.OrderShippingInclTax
// order.OrderTax
// order.CustomerId
// order.CreatedOnUtc
}
Some data needed for a polished receipt—such as the customer’s email address, billing address, line items, store URL, localized currency rendering, and order URL—comes from related nopCommerce services rather than from one flat event payload. That is normal. Treat the event as the durable trigger and use injected nopCommerce services to load the related records you need.
This approach has another advantage: your sender can intentionally minimize the data sent to Volanea. A confirmation email usually needs the recipient email, name, order identifier, totals, line summaries, and a secure order link. It does not need payment tokens, full card data, internal notes, or every property on the order entity.
Before you write code: sending prerequisites
Set up the sending side before enabling the consumer in production. If the plugin is deployed before the domain and secret are ready, valid orders may result in failed notification attempts.
Verify the sending domain in Volanea
Use a From address on a domain you have verified in Volanea, such as orders@example.com. Domain verification generally requires DNS records supplied by the provider, and you should copy the DNS hostnames and values exactly from the Volanea domain setup flow rather than guessing record names or substituting values.
Verification is not merely an administrative checkbox. It establishes that your store is authorized to send mail for the selected domain and makes it possible to publish the authentication records required for dependable mail delivery. Do not hard-code a personal mailbox or an unverified domain as the production From address.
Create a scoped operational secret
Create a Volanea secret key for the nopCommerce integration. Use test credentials for non-production stores and a separate live key for production. Separating them prevents staging traffic from contaminating production monitoring and makes revocation less disruptive if a testing environment is exposed.
Store the secret in the host environment, not in a Razor view, JavaScript bundle, public appsettings.json, source repository, or a configuration page that displays the raw value after saving. In a container deployment, use a platform secret. On a VM, use a protected environment variable or a server-side secret manager. In a managed hosting environment, use its encrypted application settings facility if available.
Decide what counts as the logical email
For an order confirmation, one logical email is normally:
order-confirmation:{OrderGuid}:{recipient-email}
That identity is important because an HTTP timeout does not prove that Volanea did not receive the request. The request could have completed remotely while the response was lost. Retrying with a new identifier risks a second email. Retrying the same operation with the same Idempotency-Key lets the API recognize it as the same logical send.
Build the plugin around a small sender service
Keep the event consumer thin. Its responsibility is to validate the event and call a service. The sender service owns recipient lookup, field mapping, HTTP authorization, timeout behavior, response handling, and idempotency.
A practical plugin might have this shape:
Nop.Plugin.Misc.VolaneaEmail/
Consumers/
OrderPlacedConsumer.cs
Services/
VolaneaEmailSender.cs
Settings/
VolaneaEmailSettings.cs
Infrastructure/
DependencyRegistrar.cs
plugin.json
The exact plugin boilerplate depends on your nopCommerce version, but the consumer pattern remains the same. Start from the plugin structure appropriate to the nopCommerce version you operate, then add the consumer and sender service.
Keep message construction separate from transport
Avoid mixing a large HTML string, customer lookup, event handling, and HttpClient calls inside one long HandleEventAsync method. Separating those concerns makes the integration testable.
A clean implementation has three layers:
- Event consumer: receives
OrderPlacedEventand invokes the workflow. - Message builder: loads permitted order and customer fields, validates them, and creates a message model.
- Volanea client: converts that model into JSON and posts it to Volanea with the right headers.
This division also makes future changes simpler. You might later replace inline HTML with Volanea templates, send an internal copy to operations, or add an order-shipped consumer without duplicating credential and retry logic.
Working C# mapping: nopCommerce order event to Volanea email
The following example illustrates the direct server-side route. It consumes the real OrderPlacedEvent, loads the customer and order items through nopCommerce services, maps the values into the Volanea send body, and calls POST https://api.volanea.com/v1/send.
The request uses a secret key in the Authorization: Bearer header and a stable Idempotency-Key. It sends plain text and HTML content so recipients who prefer text-only mail still receive a readable confirmation.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using Nop.Core.Domain.Orders;
using Nop.Services.Customers;
using Nop.Services.Events;
using Nop.Services.Orders;
public sealed class OrderPlacedConsumer : IConsumer<OrderPlacedEvent>
{
private readonly ICustomerService _customerService;
private readonly IOrderService _orderService;
private readonly VolaneaEmailSender _volaneaEmailSender;
public OrderPlacedConsumer(
ICustomerService customerService,
IOrderService orderService,
VolaneaEmailSender volaneaEmailSender)
{
_customerService = customerService;
_orderService = orderService;
_volaneaEmailSender = volaneaEmailSender;
}
public async Task HandleEventAsync(OrderPlacedEvent eventMessage)
{
if (eventMessage?.Order is null)
return;
var order = eventMessage.Order;
var customer = await _customerService.GetCustomerByIdAsync(order.CustomerId);
if (customer is null || string.IsNullOrWhiteSpace(customer.Email))
return;
var orderItems = await _orderService.GetOrderItemsAsync(order.Id);
var itemLines = orderItems.Select(item =>
$"{item.Product.Name} × {item.Quantity}");
var text = $"""
Thanks for your order.
Order: #{order.CustomOrderNumber}
Total: {order.OrderTotal:F2}
Items:
{string.Join("\n", itemLines)}
""";
var htmlItems = string.Join("", orderItems.Select(item =>
$"<li>{System.Net.WebUtility.HtmlEncode(item.Product.Name)} × {item.Quantity}</li>"));
var html = $"""
<h1>Thanks for your order</h1>
<p>Your order <strong>#{System.Net.WebUtility.HtmlEncode(order.CustomOrderNumber)}</strong> has been received.</p>
<ul>{htmlItems}</ul>
<p><strong>Total:</strong> {order.OrderTotal:F2}</p>
""";
await _volaneaEmailSender.SendOrderConfirmationAsync(
recipientEmail: customer.Email,
recipientName: customer.GetFullName(),
orderGuid: order.OrderGuid,
orderNumber: order.CustomOrderNumber,
text: text,
html: html);
}
}
public sealed class VolaneaEmailSender
{
private readonly HttpClient _httpClient;
private readonly string _apiKey;
private readonly string _from;
public VolaneaEmailSender(HttpClient httpClient)
{
_httpClient = httpClient;
_apiKey = Environment.GetEnvironmentVariable("VOLANEA_API_KEY")
?? throw new InvalidOperationException("VOLANEA_API_KEY is not configured.");
_from = Environment.GetEnvironmentVariable("VOLANEA_FROM_EMAIL")
?? throw new InvalidOperationException("VOLANEA_FROM_EMAIL is not configured.");
}
public async Task SendOrderConfirmationAsync(
string recipientEmail,
string recipientName,
Guid orderGuid,
string orderNumber,
string text,
string html)
{
var payload = new
{
from = _from,
to = new[] { recipientEmail },
subject = $"We received your order #{orderNumber}",
text,
html
};
using var request = new HttpRequestMessage(
HttpMethod.Post,
"https://api.volanea.com/v1/send");
request.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", _apiKey);
// One key for one logical receipt. Reuse this exact value on a retry.
request.Headers.Add(
"Idempotency-Key",
$"nop-order-confirmation-{orderGuid}-{recipientEmail.ToLowerInvariant()}");
request.Content = new StringContent(
JsonSerializer.Serialize(payload),
Encoding.UTF8,
"application/json");
using var response = await _httpClient.SendAsync(request);
var responseBody = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException(
$"Volanea send failed: {(int)response.StatusCode} {responseBody}");
}
}
}
Field mapping explained
The mapping above deliberately stays simple:
| nopCommerce source | Volanea request field | Why it is used |
|---|---|---|
| Server environment variable | from | The verified operational sending address. |
customer.Email | to[0] | The address receiving the order confirmation. |
order.CustomOrderNumber | subject | A customer-friendly reference for the order. |
| Order items and totals | text | Accessible plain-text receipt content. |
| Order items and totals | html | Formatted receipt content for HTML-capable clients. |
order.OrderGuid + recipient | Idempotency-Key header | Stable identity for one confirmation email. |
If you use a saved Volanea template instead of inline html and text, keep the same event architecture and idempotency approach. Only the message body portion changes. Refer to the Volanea API reference and setup guides before switching request formats, because an API is intentionally strict about accepted fields and template-related parameters.
A note on totals and formatting
The sample displays order.OrderTotal with a simple numeric format for clarity. In a real store, format amounts with the store’s configured currency and locale services. A buyer in one market may expect a comma decimal separator, while another expects a period; currency symbols and tax wording may also differ.
Likewise, product names can include characters that must be HTML encoded. Never concatenate customer-entered address fields, gift-card messages, product attributes, or custom order properties into HTML without encoding. Email markup is still an output surface.
Where the Volanea API key belongs
The API key belongs in server-only configuration accessible to the nopCommerce process. The code example reads VOLANEA_API_KEY from an environment variable because environment variables work cleanly across many hosting setups and avoid putting the secret in a project file.
A production deployment typically provides at least these values:
VOLANEA_API_KEY=sk_live_replace_with_real_secret
VOLANEA_FROM_EMAIL=orders@example.com
Do not commit these values to Git. Do not paste the key into a public support ticket. Do not expose it in an admin page that returns the current value to every administrator. If your plugin provides a configuration screen, store only non-secret settings there when possible—such as whether confirmations are enabled or a sender display name—and retrieve the secret from environment-level configuration.
Why client-visible configuration is unsafe
A secret placed in frontend code is not protected by minification, obfuscation, HTTPS, or a hidden form field. Visitors can inspect source maps, network requests, cached assets, extensions, and browser memory. A secret used from the browser also gives attackers a path to make direct API calls outside your store’s authorization model.
Keeping the send inside the nopCommerce server process also lets you enforce application rules before mail leaves the system. For example, you can decline to send when the email is blank, when the order is a test order, when a store is in maintenance mode, or when a notification record has already been marked complete.
Rotation and incident response
Plan for the day a key must be replaced. Use a named environment variable rather than scattering the value through classes. Then rotation is a deployment configuration change followed by an application restart or reload, not a code rewrite.
If you suspect exposure, revoke or rotate the key in Volanea, update the deployment secret, and inspect send activity for unexpected messages. The smaller the scope and number of systems holding the key, the easier that response becomes.
Make duplicate sends unlikely, not merely inconvenient
An order email is a side effect. The customer may place one order, while the event handler may run more than once due to a restart, a retry, a database recovery path, a code bug, or a timeout after Volanea has already accepted the request.
Volanea supports the Idempotency-Key header on POST /v1/send. Use it on the first attempt and reuse exactly the same key for retried attempts. Do not create a new random key inside every retry loop; doing so tells the receiving API that each retry is a new logical send.
Use a stable key derived from business identity
For the basic confirmation use case, the key in the example is based on:
nop-order-confirmation-{OrderGuid}-{normalized-recipient-email}
OrderGuid is a better identifier than a display order number when uniqueness matters across stores or migration histories. The message type is included so a future shipment notice for the same order does not collide with the confirmation. The recipient is included so a deliberate additional confirmation to a separate permitted address can remain a distinct action.
Avoid building the key from volatile content such as the total, product names, current date, rendered HTML, or a random GUID created at send time. Those values can change on a rerun, defeating de-duplication.
Add your own delivery ledger for critical flows
API-level idempotency protects the HTTP call, but an application-level ledger gives you operational visibility. For high-value messages, create a plugin table such as VolaneaNotificationAttempt with:
- Notification type, such as
order-confirmation. - nopCommerce order ID and order GUID.
- Recipient address.
- Idempotency key.
- Attempt count and timestamps.
- Volanea response status or message identifier where available.
- Final state: pending, accepted, retryable failure, permanent failure, or suppressed.
Before sending, atomically claim the notification record. If a second event handler reaches the same order, it sees the existing record rather than independently sending. This is stronger than relying solely on in-memory locking, which disappears after a process restart and does not coordinate across multiple application instances.
When this breaks: failures in the nopCommerce-to-Volanea hop
No email integration is just “call an endpoint.” This specific hop includes an order event, a plugin handler, related-data lookup, outbound HTTPS, API authentication, provider validation, and email delivery. Design for the places it can fail.
nopCommerce retries or duplicate event handling cause duplicate attempts
A retry can happen after an exception, a deployment interruption, or an ambiguous network error. The dangerous case is a timeout after Volanea accepted the send but before nopCommerce received the response. Your handler cannot reliably infer whether the email was created from the timeout alone.
Mitigation: reuse the stable Idempotency-Key for every retry of the same logical message. For important communications, pair it with a durable notification ledger and a unique database constraint on the notification identity.
Do not solve duplicates by disabling all retries. That trades duplicates for silent missing receipts. The correct goal is safe retries.
The outbound request times out
A slow network, proxy, DNS outage, or temporary API delay can make HttpClient.SendAsync fail or exceed your chosen timeout. If the request never reached Volanea, a retry is appropriate. If the request reached Volanea but the response was lost, the same retry is still appropriate only when it carries the same idempotency key.
Mitigation: configure a finite HTTP timeout, record the failure with order context, retry transient failures with backoff, and reuse the same key. Do not retry authentication failures, malformed request failures, or domain-verification failures indefinitely; those require configuration changes.
Avoid doing long repeated retries inside the checkout request path. If your event is handled inline, prolonged waiting can make order placement feel slow. A better production design records a pending notification and lets a scheduled task process retries outside the customer’s checkout response.
Payload fields are missing or invalid on some orders
Not every order has the same data shape. Guest checkout customers may lack a complete profile. Certain products may be deleted or renamed after ordering. Multi-store deployments may have different sender addresses. A custom checkout could create orders without an email address in the field your code expects. A customer name may be empty, and a custom property may be absent.
Mitigation: validate required fields before calling Volanea. At minimum, require a non-empty recipient email, a verified configured sender address, and an order identifier. Use safe fallbacks for optional display fields, such as Customer when a full name is unavailable. Log validation failures without logging unnecessary personal data.
The integration should not assume that one generic object contains every value. Load order items, addresses, localization data, and store context through the relevant nopCommerce services, and test guest, registered, tax-exempt, multi-store, digital, shippable, discounted, and refunded order scenarios.
The From domain is not verified
A request can be structurally valid but still fail because the From address uses a domain that has not completed verification. This is a configuration issue, not a transient delivery condition.
Mitigation: verify the domain before enabling the plugin, keep sender addresses in server configuration, and separate test and production settings. When changing sender domains, deploy the DNS and verification changes first, then switch the application setting.
Volanea accepts the API request but a recipient does not receive the message
An accepted send is not the same thing as inbox placement. A recipient address may be suppressed after a hard bounce, may have opted out where applicable, may reject mail at its server, or may classify the message differently from another mailbox provider.
Mitigation: use Volanea’s email activity and delivery events to distinguish accepted, delivered, bounced, complained, and suppressed outcomes. Keep transactional content clear and expected, maintain authenticated sending domains, and avoid retrying known permanent failures to the same address.
Queue and retry strategy for a production store
For a small store, a direct event consumer may be enough initially. For a busy store or any flow where checkout latency matters, move external sending onto a durable workflow.
A robust model looks like this:
OrderPlacedEventcreates or claims a notification record in your plugin database.- The consumer records the event quickly and returns.
- A nopCommerce scheduled task finds pending records.
- The task posts to Volanea using the stored idempotency key.
- The task marks accepted sends complete and schedules only transient failures for later retry.
- Permanent failures are visible to staff for investigation rather than retried forever.
nopCommerce supports scheduled tasks through IScheduleTask, which makes this a native extension pattern rather than an external polling workaround. The scheduled task should be small, bounded, and safe to run repeatedly.
Suggested retry classification
Use a deliberate classification policy:
- Retryable: connection failure, timeout, temporary server error, gateway error, or rate-related response where the provider indicates retrying is appropriate.
- Non-retryable until fixed: invalid API key, unverified sender domain, malformed body, missing recipient, or an invalid From address.
- Business decision required: recipient suppression, customer opt-out state, or an order that was canceled before the notification job ran.
Store enough context to make diagnosis possible: the order GUID, notification type, response status, safe error summary, and attempt timestamps. Do not store full payment or address information merely because it is available on the order.
Testing the integration before enabling it
Start with a non-production store and a Volanea test key where available. Create a real order through the checkout path that publishes OrderPlacedEvent; do not test only by manually calling the sender method. You want to confirm the complete route from order placement to event discovery to API request.
Test cases worth running
- A registered customer places an order with one physical product.
- A guest customer places an order.
- An order contains multiple items and quantities.
- An order has a missing first name or last name.
- An order is placed twice in an intentionally repeated test scenario.
- A forced HTTP timeout occurs after the request is initiated.
- The API key is deliberately invalid.
- The From address is deliberately changed to an unverified domain in a test environment.
- An order is created for a store with different currency or localization settings.
For duplicate protection, invoke the sender twice with the same OrderGuid, recipient, and notification type. Confirm that both calls use an identical Idempotency-Key and inspect Volanea activity to verify the result is not two independent messages.
Keep test mail separate
Use a test From address and an isolated recipient mailbox. Do not use real customer lists to validate a new integration. It is easy to accidentally send a development receipt that looks authentic, especially if the template includes an order number and branded design.
When the basic path works, add monitoring before broad rollout. A failure log without alerting is useful only when somebody happens to look at it.
Alternatives and when they make sense
The direct custom-plugin approach is the best fit when you control the nopCommerce deployment and need an email triggered from the actual order event. It is server-side, secret-safe, and does not depend on a third-party automation platform being available during checkout.
Use SMTP for built-in nopCommerce mail instead
If your only goal is changing the delivery provider for nopCommerce’s existing system-generated emails, SMTP can be a lower-code option. nopCommerce already has email-account settings and an email queue, so SMTP may fit messages the platform already generates.
However, SMTP is not equivalent to an event-driven REST integration. A REST client can apply per-message idempotency, structured data mapping, explicit response inspection, and a tailored workflow. Use the approach that matches the message source: built-in notification pipeline for built-in templates, or a custom event consumer for custom lifecycle messages.
Use middleware only when your architecture requires it
If your nopCommerce installation cannot accept custom code or plugins, a middleware approach may be necessary—but nopCommerce does not provide a standard OrderPlacedEvent outbound HTTP webhook configuration you can simply point at Zapier or Make. You would still need a plugin or other server-side customization to emit the order event externally.
Middleware can be useful when many systems need the same order event, when you need a centralized queue, or when an organization already has an integration service. In that case, have the nopCommerce plugin post a minimal signed event to your middleware, and let the middleware call Volanea. Do not make the middleware endpoint public and unauthenticated.
Operational checklist
Before turning on production sends, verify the following:
- The nopCommerce version and plugin target framework match.
- Your consumer implements
IConsumer<OrderPlacedEvent>. - The handler validates the event, recipient address, and configured sender.
- The From domain is verified in Volanea.
VOLANEA_API_KEYis available only to the server process.- Test and production keys are separate.
- Every logical transactional email uses a stable
Idempotency-Key. - Retry logic reuses the key instead of generating another one.
- A failure path records actionable diagnostics.
- HTML values are encoded before rendering.
- Checkout is not blocked by long retry loops.
- Staff can find and resolve permanently failed notifications.
The result is a straightforward architecture: nopCommerce records an order, publishes OrderPlacedEvent, your plugin maps the minimum required order data, and Volanea receives one authenticated REST request for one logical customer message. That is a more reliable foundation than exposing a provider secret in the browser or pretending a marketplace app exists when it does not.
FAQ
Does Volanea have a native nopCommerce plugin?
No. This guide uses a custom nopCommerce plugin and the platform’s built-in event-consumer model. It does not rely on a Volanea marketplace listing or an install-from-marketplace flow.
What trigger sends the email in this integration?
The trigger is nopCommerce’s OrderPlacedEvent. The consumer receives an OrderPlacedEvent object containing the placed Order, then loads any related customer and item data needed to construct the message.
Does nopCommerce send an order JSON webhook by default?
Not for this flow. OrderPlacedEvent is an in-process C# event, not a standard outbound HTTP webhook payload. Your plugin constructs the Volanea JSON request from the event and related nopCommerce entities.
Where should the Volanea API key be stored?
Store it in a server-only environment variable, managed secret store, or protected deployment setting. Never put it in browser JavaScript, public configuration, source control, or a storefront page.
How do I prevent duplicate order emails after a timeout?
Send an Idempotency-Key with POST /v1/send, derive it from stable business data such as the order GUID and notification type, and reuse that exact key for retries. For critical notifications, also keep a durable plugin-side notification ledger.