If you need to send email from Salesforce when a deal closes, a case changes, or a custom record is created, you do not need a marketplace package to connect Salesforce to Volanea. A record-triggered Flow can start the process, while Apex and a Named Credential provide the secure server-side HTTP hop to Volanea’s email API.

Volanea does not provide a native Salesforce app or Salesforce AppExchange installation. That is important because it changes the integration design: Salesforce remains the system that decides when an email should be sent, and Volanea becomes the delivery service that accepts a structured API request and sends the message.

This guide uses a concrete example: send a welcome email when a Salesforce Opportunity moves to the Closed Won stage. The same pattern works for Leads, Cases, Contacts, custom objects, Experience Cloud registrations, or any other record that can start a record-triggered Flow.

What the Salesforce-to-Volanea integration looks like

The reliable production pattern has four parts:

  1. A Salesforce record-triggered Flow detects a business event, such as an Opportunity moving to Closed Won.
  2. The Flow passes the recipient address and business data to an invocable Apex action.
  3. Apex queues an asynchronous HTTP callout and sends a JSON request to Volanea.
  4. Volanea accepts the request, queues the email, and returns a response that Salesforce can log for operational visibility.

This is intentionally different from sending mail through Salesforce’s built-in email facilities. Salesforce can send email itself in many contexts, but an email API provider is useful when you need a dedicated sending domain, deliverability controls, transactional message infrastructure, sending analytics, a shared API for your application stack, or a clean separation between CRM automation and mail delivery.

A record-triggered Flow is the right starting point because it uses Salesforce’s real data model. It runs when a record is created or updated, lets administrators own the business condition, and does not require exposing a Volanea key in a browser, Lightning component, screen flow, or custom metadata record.

Choose a real Salesforce trigger

Salesforce Flow Builder supports record-triggered flows. For this implementation, create a flow on the standard Opportunity object and set it to run when a record is updated.

The concrete trigger is:

An Opportunity record is updated to meet the condition StageName Equals Closed Won.

In Flow Builder, use an after-save record-triggered flow, also described in Salesforce as an Actions and Related Records flow. Configure entry conditions so the flow runs only when the Opportunity is updated to meet the condition requirements. This distinction matters: if the flow is allowed to run on every update while an Opportunity remains Closed Won, routine edits to amount, close date, or owner can send the welcome message again.

For a useful first version, collect these Opportunity fields:

  • Opportunity.Name for the customer-facing reference or purchase name.
  • Opportunity.Amount for a formatted purchase amount, if appropriate.
  • Opportunity.AccountId to locate the associated Account.
  • An email field, such as a custom Billing_Email__c on Opportunity, or a Contact email retrieved through a related object.
  • A stable record identifier, Opportunity.Id, to use as an idempotency key and for logging.

A standard Opportunity does not contain a built-in recipient email field. Do not assume Account has a universally populated email address either. Decide where your organization stores the operational recipient address. A common implementation uses a custom field named Billing_Email__c on Opportunity, validates it before the Flow invokes Apex, and treats it as the destination for the transactional message.

Other trigger examples

The technical callout does not change when the business event changes. Common record-triggered Flow conditions include:

  • A Lead is created with Status = New, which can start a lead acknowledgement email.
  • A Case is updated and Status = Closed, which can send a resolution summary.
  • A custom Subscription__c record is created with Status__c = Active, which can send onboarding instructions.
  • A Contact record changes and an opt-in field becomes true, which can start a confirmed subscription or preference message.

The condition should express a genuine business event, not merely the current state of a record. “Updated to meet condition requirements” is usually safer than “created or updated,” because the former gives the Flow a natural guard against ordinary follow-up updates.

Why Apex is the practical outbound HTTP layer

Salesforce can make outbound HTTP requests. For an integration that must transform data, keep a secret, implement idempotency, and avoid slowing down record saves, Apex is the most controlled option.

Salesforce also offers declarative HTTP callout capabilities through Flow when an external service and authentication are configured. That can be attractive for a simple, stable API contract. In practice, an invocable Apex action is more suitable for a transactional email workflow because it can validate inputs, serialize a precise payload, queue the work asynchronously, interpret API responses, and write a durable delivery-attempt record.

The important Salesforce constraint is that a callout should not be treated as an inline, user-visible part of an Opportunity update. An external service can be slow, temporarily unavailable, rate limited, or return an error. The record save should complete independently. The code below uses a Queueable Apex job implementing Database.AllowsCallouts, which moves the HTTP request out of the Flow transaction.

This gives you a clear operational boundary:

  • Salesforce successfully saved the Opportunity.
  • Salesforce queued an email-delivery attempt.
  • The callout either received an API response or failed and was logged.

That boundary is much easier to reason about than making an untracked request directly from a front-end component.

Configure the Volanea secret with a Named Credential

Never place a Volanea API key in a Lightning Web Component, Visualforce page, JavaScript bundle, Flow text template, custom label, or custom metadata value that an administrator or user may inadvertently expose. A browser-visible key can be copied and used to send mail as your organization.

Use Salesforce Named Credentials and External Credentials instead. These are Salesforce’s server-side mechanism for associating a callout endpoint with authentication details. The code refers to the credential by name and does not contain the base URL secret or the API key.

Create a Named Credential with an API name such as Volanea_API and configure it for the Volanea API base URL. Create an External Credential with a custom authentication parameter for the Volanea API key, then map that parameter to the required authorization header. Grant the principal access through a permission set assigned to the users or integration context that run the Flow.

For Volanea’s bearer-token API authentication, the resulting outbound request must carry this header:

Authorization: Bearer YOUR_VOLANEA_API_KEY

Store the actual key in the External Credential’s principal configuration, not in the Apex source. The Apex endpoint below begins with callout:Volanea_API; Salesforce resolves that name to the configured URL and injects authentication according to the Named Credential configuration.

Minimum access model

A secure configuration should include all of the following:

  • A dedicated Volanea API key with only the access required for sending.
  • One Salesforce External Credential principal that holds that key.
  • A permission set granting access to the External Credential principal.
  • Named Credential access restricted to the integration’s intended users and automation context.
  • No API key in source control, debug logs, Flow formulas, custom notifications, or client-side code.

Rotate the key if it is ever pasted into a ticket, committed to a repository, or shown in a debug log. When rotating, update the External Credential configuration rather than changing deployed Apex.

For endpoint details, message options, and API updates, keep the implementation aligned with the Volanea email API reference rather than hard-coding assumptions into Flow documentation.

Build the record-triggered Flow

Create an after-save record-triggered Flow on Opportunity. Set the trigger to run when a record is updated, add the entry condition StageName Equals Closed Won, and select the option that runs the flow only when the record is updated to meet those conditions.

Next, add a Get Records element if the recipient email lives on a related record. If Billing_Email__c is an Opportunity custom field, a Get Records element is unnecessary. Add a Decision element before the Apex action to ensure the email field is not blank. This prevents avoidable API calls and gives you a place to route incomplete records to an internal remediation path.

Then add an Action element and select the invocable Apex action exposed by the class in the next section. Map Flow resources as follows:

Flow valueApex inputVolanea message field
$Record.IdopportunityIdidempotencyKey
$Record.Billing_Email__crecipientEmailto
$Record.NameopportunityNameused in subject and html
$Record.Amountamountused in html

Use a Salesforce formula resource for amount if you need locale-specific currency formatting. The code below treats it as text because the exact formatting is a customer experience decision, not an API requirement.

Finally, activate the Flow and test with a sandbox record. Create an Opportunity in a pre-close stage, populate the recipient email, then update StageName to Closed Won. Testing from a pre-close state matters: a test that edits an already-closed Opportunity may not satisfy the “updated to meet condition requirements” entry logic.

Apex callout code and field mapping

The following example consists of an invocable action for Flow and a Queueable worker for the HTTP callout. It sends a Volanea email request to POST /v1/emails through the Volanea_API Named Credential.

The JSON sent to Volanea has this shape:

{
  "from": "Sales Team <hello@example.com>",
  "to": ["buyer@example.com"],
  "subject": "Welcome — your Acme order is confirmed",
  "html": "<h1>Thanks for your order</h1><p>...</p>",
  "text": "Thanks for your order...",
  "idempotencyKey": "006XX00000ABC123"
}

Replace the from value with an address on a domain you have verified in Volanea. Do not use a recipient address or an arbitrary employee mailbox as the sender.

public with sharing class VolaneaOpportunityEmailAction {
    public class RequestInput {
        @InvocableVariable(required=true)
        public String opportunityId;

        @InvocableVariable(required=true)
        public String recipientEmail;

        @InvocableVariable(required=true)
        public String opportunityName;

        @InvocableVariable
        public String amount;
    }

    @InvocableMethod(
        label='Send Closed Won Email with Volanea'
        description='Queues a Volanea transactional email after an Opportunity closes.'
    )
    public static void send(List<RequestInput> inputs) {
        for (RequestInput input : inputs) {
            if (String.isBlank(input.recipientEmail)) {
                continue;
            }
            System.enqueueJob(new VolaneaEmailJob(input));
        }
    }

    public class VolaneaEmailJob implements Queueable, Database.AllowsCallouts {
        private RequestInput input;

        public VolaneaEmailJob(RequestInput input) {
            this.input = input;
        }

        public void execute(QueueableContext context) {
            String safeName = String.escapeSingleQuotes(input.opportunityName);
            String safeAmount = String.escapeSingleQuotes(
                String.isBlank(input.amount) ? '' : input.amount
            );

            Map<String, Object> payload = new Map<String, Object>{
                'from' => 'Sales Team <hello@example.com>',
                'to' => new List<String>{ input.recipientEmail },
                'subject' => 'Welcome — your ' + input.opportunityName + ' order is confirmed',
                'html' => '<h1>Thanks for your order</h1>' +
                    '<p>Your opportunity <strong>' + safeName + '</strong> is now confirmed.</p>' +
                    '<p>Amount: ' + safeAmount + '</p>',
                'text' => 'Thanks for your order. ' + input.opportunityName +
                    ' is now confirmed. Amount: ' + safeAmount,
                'idempotencyKey' => input.opportunityId
            };

            HttpRequest request = new HttpRequest();
            request.setEndpoint('callout:Volanea_API/v1/emails');
            request.setMethod('POST');
            request.setHeader('Content-Type', 'application/json');
            request.setHeader('Accept', 'application/json');
            request.setTimeout(10000);
            request.setBody(JSON.serialize(payload));

            Http http = new Http();
            HttpResponse response = http.send(request);

            Integer status = response.getStatusCode();
            if (status < 200 || status >= 300) {
                throw new CalloutException(
                    'Volanea send failed: HTTP ' + status + ' ' + response.getBody()
                );
            }
        }
    }
}

The mapping is deliberate. Salesforce’s $Record.Id becomes idempotencyKey, the email destination becomes a one-item to array, and the Opportunity name is used to create both a human-readable subject and message content. The text part is included alongside HTML so recipients and mail clients that prefer plain text still receive a useful message.

For production HTML, use a reviewed template rather than building a substantial document inside Apex. Keep dynamic values escaped for HTML context. The short example shows the data flow, but a full template should account for HTML escaping, locale-specific money formatting, links, brand assets, and legal footer requirements.

Deliverability and sender identity

An API request that returns success is not the same as an email that reaches the inbox. Before activating the Flow for real customers, configure and verify the sending domain in Volanea. Domain authentication typically involves DNS records for sender authentication and alignment; obtain the exact records from Volanea’s domain setup instructions and publish them in your DNS provider.

Use a From address that matches the purpose of the message. A sales confirmation might come from hello@yourdomain.com; a support case resolution might use support@yourdomain.com. Keep the reply-to behavior intentional. If replies need to reach a human team, set the appropriate reply address in the API payload according to the current API reference.

Salesforce data quality directly affects deliverability. A malformed, stale, role-based, or mistyped recipient address can produce bounces and degrade sending quality. Validate the email before creating the business event when possible, and consider checking an address with the email verification tool before relying on it for important transactional communication.

Keep transactional and marketing intent separate

A Closed Won confirmation is generally transactional because it confirms a customer action or commercial relationship. That does not automatically make every follow-up email transactional. Promotional cross-sells, newsletters, and product announcements need consent and preference handling appropriate to your jurisdiction and policy.

Do not use a CRM stage change as a shortcut around consent rules. Model marketing consent and suppression data explicitly in Salesforce, and add Flow decisions that prevent promotional sends to people who have opted out.

When this breaks: failures specific to this hop

Every integration needs a failure model. The Salesforce-to-Volanea hop has several predictable failure modes, and handling them up front prevents duplicate confirmations, silent misses, and difficult investigations.

Salesforce retries can create duplicate sends

Asynchronous automation and integration retries are useful, but they can produce an at-least-once delivery pattern. A request can reach Volanea successfully, while Salesforce fails before it records the success or receives a response. When the job is retried, the same logical message can be sent again.

That is why the example maps the stable Salesforce Opportunity.Id to idempotencyKey. Use the same key for the same business event. If you need a new message after a later meaningful event, construct a distinct key, such as the record ID plus a specific event version or timestamp that is persisted on the record. Do not generate a random key inside each retry attempt; doing so defeats deduplication.

Also protect the Flow trigger. “Updated to meet condition requirements” helps with normal record edits, but your business process should define what happens if an Opportunity is reopened and closed again. You may want a custom checkbox such as Closed_Won_Email_Sent__c or a dedicated delivery-attempt object, updated only after a confirmed API response, depending on whether a repeat close should send another email.

HTTP timeouts are ambiguous

The code sets a 10-second timeout. A timeout does not prove Volanea did not receive the request. It only proves Salesforce did not receive a response within the configured time. Retrying without idempotency can therefore send duplicates.

Treat a timeout as an unknown outcome. Log the Salesforce record ID, idempotency key, endpoint path, status or exception, and job ID. Then retry with the same idempotency key. Avoid logging the API key, full message body, or unnecessary personal data.

For more mature implementations, create a custom object such as Email_Delivery_Attempt__c with fields for source record ID, event type, idempotency key, recipient, attempt number, API status, response reference, error category, and next retry time. A scheduled Apex job can retry transient failures while routing permanent failures—such as invalid recipient data or an unauthorized credential—to an administrator queue.

Payload fields can be blank or unavailable

Salesforce permissions, field-level security, incomplete integrations, record types, and plan-specific data availability can all mean that a field is absent or blank when the Flow runs. A custom Billing_Email__c field may not be populated for every Opportunity. A related Contact may be missing. An amount may be blank for nonstandard deals.

Validate required fields before enqueueing the job. The recipient address is mandatory; the amount is not. If the email is missing, send an internal task, post to an error queue, or mark the record for follow-up instead of calling the API with an empty destination.

If your organization relies on Salesforce features or data enrichment that are not available in a particular edition, do not assume the Flow will have equivalent fields. Test with the actual profiles, record types, and licenses used in production. A Flow that works for a system administrator can fail for a restricted integration user because that user cannot access a referenced field or credential principal.

Authentication failures need a different response

An HTTP 401 or 403 is not a transient recipient problem. It generally indicates a bad, revoked, expired, or unauthorized credential configuration. Retrying it repeatedly only creates noise. Alert an administrator, verify Named Credential and External Credential access, confirm the Authorization header configuration, and rotate the Volanea key if needed.

Similarly, a 4xx validation response should be treated differently from a 5xx response. Invalid input usually requires a data fix. A server-side error or temporary rate limit may be eligible for delayed retry. Store enough response metadata to make that distinction without persisting sensitive email content unnecessarily.

Testing, observability, and rollout

Start in a Salesforce sandbox with a non-production Volanea environment or a controlled recipient address, if available in your account configuration. Verify the entire chain: record update, Flow interview, queued Apex job, outbound request, API acceptance, and message arrival.

A useful test matrix includes:

  1. A valid Closed Won Opportunity with a valid recipient address.
  2. An Opportunity with a blank recipient address, which should not queue a send.
  3. An update to an already Closed Won record, which should not send again under the selected Flow criteria.
  4. An intentionally invalid credential, which should surface as an actionable authentication failure.
  5. A simulated timeout or temporary endpoint failure, followed by a retry using the same idempotency key.
  6. A recipient mailbox that can confirm both HTML and plain-text rendering.

Use Salesforce debug logs carefully during development. They can show Flow execution and callout errors, but avoid adding debug statements that print credentials or full recipient data. In production, custom delivery-attempt records and targeted alerting are more durable than relying on short-lived debug logs.

Roll out to a small internal segment first. That allows you to validate sender identity, reply handling, content rendering, suppression behavior, and the business rule that decides when a record is eligible. Once the event semantics are proven, expand the Flow to the intended records.

Alternatives and when to use them

A direct Apex callout is not the only way to connect Salesforce to an email API, but it is often the clearest option when Salesforce is the authoritative event source.

Salesforce Flow HTTP Callout can work for straightforward requests when your team prefers declarative configuration and the API schema is stable. It still needs secure authentication through Named Credentials, deliberate field mapping, and an answer for timeout and duplicate behavior.

Middleware platforms can be appropriate when you need to join multiple systems, transform data without Apex, or route events through a central automation layer. The trade-off is another hop: Salesforce sends an event to the middleware, the middleware calls Volanea, and both systems need monitoring and retry controls. A middleware route is not required merely because Volanea has no native Salesforce app; Salesforce can make outbound HTTP callouts through Apex.

Salesforce’s own email features are a reasonable alternative for simple internal notifications or low-complexity workflows. Use an API-driven delivery service when you need consistent sending infrastructure across Salesforce and your product, more control over sender-domain operations, or a programmatic message lifecycle outside the CRM.

A durable operating model

The strongest version of this integration treats an email send as a business event with its own state, rather than as a hidden side effect of a Flow. The Opportunity becomes the source event; an email-attempt record captures delivery work; Volanea receives the message request; and retry logic is based on response class and idempotency.

That model pays off when your volume grows. Support teams can answer whether a confirmation was attempted. Developers can distinguish a Salesforce data problem from a Volanea API error. Administrators can change the Flow’s business condition without editing authentication code. And a customer does not receive multiple copies because an ambiguous timeout triggered a blind resend.

Begin with the focused Closed Won use case, use a verified sender domain, preserve a stable idempotency key, and add logging before the workflow becomes business-critical. Those fundamentals make it practical to extend the same pattern to onboarding, case updates, invoices, renewal notices, and other Salesforce-driven transactional email.

FAQ

Can Salesforce send an HTTP request to Volanea without a marketplace app?

Yes. Salesforce supports outbound HTTP callouts through Apex, and Apex can be invoked from a record-triggered Flow. A native AppExchange package is not required for this approach.

Where should the Volanea API key be stored in Salesforce?

Store it in a Salesforce External Credential used by a Named Credential. Do not put it in Apex source code, Flow text, custom metadata, custom labels, browser code, or any other client-visible configuration.

How do I prevent duplicate emails when an Opportunity is edited again?

Configure the record-triggered Flow to run only when the Opportunity is updated to meet the Closed Won condition. Also use a stable idempotency key, such as the Opportunity ID, for retries and ambiguous timeout handling.

Can I use a Lead, Case, or custom object instead of Opportunity?

Yes. Create the record-triggered Flow on the relevant object, map its email and business fields into the invocable Apex inputs, and retain the same Named Credential, asynchronous callout, validation, and idempotency pattern.

What should happen if the recipient email field is blank?

Do not call the email API. Add a Flow Decision element that routes incomplete records to a task, error queue, or custom exception record so the data can be corrected before a send is attempted.