Slack is often where a team notices an urgent request, approves an exception, or collects the details that should trigger a customer notification. This guide shows how to send email from Slack with Volanea without pretending there is a native Volanea Slack Marketplace app.
Volanea does not ship a native Slack app, marketplace listing, or one-click Workflow Builder connector. The direct route is a Slack workflow that invokes a custom function. That function runs TypeScript in Slack’s hosted Deno environment, reads a server-side Volanea key from Slack environment variables, then calls Volanea’s REST API at POST https://api.volanea.com/v1/send.
The result is a practical internal-email workflow: a teammate starts a workflow from Slack, submits a form with the recipient and message context, and the workflow sends a transactional email from your verified Volanea domain. It is useful for manually approved customer updates, incident communications, quote follow-ups, access invites, escalation notices, and other sends where Slack is the operational control plane rather than the system of record.
What the Slack-to-email integration actually is
The important constraint is architectural: Slack does not provide a built-in, arbitrary outbound-HTTP action in no-code Workflow Builder that you can safely point at Volanea with a bearer token. Slack does provide workflows, triggers, built-in functions, connector functions, and custom workflow steps. For this integration, the correct direct path is a custom function that uses Deno’s fetch() API to call Volanea.
That means the flow looks like this:
- A person starts a Slack workflow from a link trigger.
- Slack opens a form using its built-in
OpenFormfunction. - The person submits the form.
- Slack passes the submitted form field values to your custom function as typed workflow inputs.
- The custom function builds a Volanea message payload and makes an authenticated
POST /v1/sendrequest. - Volanea accepts the message for processing, applies suppression and delivery controls, and records the send.
There is no Volanea token in a Slack message, a Workflow Builder text field, browser JavaScript, or a public webhook URL. The secret belongs in the Slack app’s deployed environment variables and is read only while the custom function runs.
This distinction matters because an incoming Slack webhook and a Slack custom workflow function are different models. A webhook is generally an HTTP endpoint that receives an event. In this guide, Slack is the caller: its custom function makes the outbound HTTPS call to Volanea. The custom function is the secure boundary between Slack form data and an email provider credential.
The concrete Slack trigger: a link trigger and form submission
For a human-approved send, use a link trigger. In Slack’s workflow model, a link trigger creates a workflow URL that a permitted Slack user can open. The workflow then uses an OpenForm step to collect information. The concrete event that starts the email action is the user’s form submission.
A link trigger is a better fit than trying to send an email for every message posted in a channel. It makes the decision explicit, gives the sender a structured form, and produces a repeatable payload. It also reduces the risk that a copied email address or a casual message accidentally triggers a customer-facing email.
Configure the form with fields such as:
recipient_email: the destination email address.recipient_name: optional display name for the recipient.customer_name: name used in the message body.subject: email subject line.message: the approved customer-facing content.case_id: a ticket, incident, order, or CRM identifier.
You can optionally include a choice field for message type, such as incident_update, access_invite, or account_notice. Do not include a free-form from address in the form. The sending identity should be configured in code so Slack users cannot accidentally choose an unverified or inappropriate sender address.
What Slack passes to your custom function
Slack does not POST a generic raw JSON webhook payload directly to Volanea in this pattern. After a form is submitted, the workflow invokes your custom function and provides the form values as the function’s inputs object.
Conceptually, your custom function receives data shaped like this:
{
"inputs": {
"recipient_email": "alex@example.com",
"recipient_name": "Alex Rivera",
"customer_name": "Alex",
"subject": "Your access request is approved",
"message": "Your workspace access is active. Reply if you need help.",
"case_id": "SUP-1842"
}
}
The exact workflow execution envelope is Slack-managed, so your application code should treat inputs as the supported interface rather than depending on undocumented transport metadata. Define every required field in the custom function schema. That gives Slack validation before the function executes and gives your implementation predictable values to map.
For a production workflow, make recipient_email, subject, message, and case_id required. The case ID is especially useful because it becomes the stable identifier for idempotency and troubleshooting.
Prerequisites before you send email from Slack
Set up the email side before deploying any Slack code. A technically successful API call is not enough if the sender is not authenticated or the recipient cannot understand the message in a non-HTML client.
You need:
- A Volanea project and a verified sending domain.
- A Volanea secret API key, beginning with
sk_for live delivery orsk_test_for non-delivering test sends. - A sender address on your verified domain, such as
support@example.com. - A Slack workspace and permission to develop and install a workflow app.
- A Slack plan that supports custom workflow steps. Slack’s developer documentation notes that custom workflow steps cannot be developed on the Free plan.
- The Slack CLI and a Slack workflow app created with the Deno Slack SDK.
Before live testing, use a Volanea test key if your project has one. A key prefixed sk_test_ lets you validate the request and rendered message without delivering it to recipients. That makes it safer to verify mappings, HTML escaping, and Slack form behavior before moving to a live secret key.
For the email payload, include both html and text. HTML creates a readable branded email, while the plain-text alternative provides a useful fallback for clients that disable or cannot render HTML. It also makes message review simpler when someone inspects a raw email.
If you still need to choose a plan for operational sends, review transactional email pricing after you have estimated workflow volume, expected recipients per send, and whether this is a low-volume approval flow or part of a larger automation system.
Build the custom Slack function
A Slack custom function has two pieces: a definition that declares inputs and outputs, and an implementation that runs when the workflow reaches that step. The implementation below is intentionally narrow. It accepts approved form data, escapes it before embedding it in HTML, calls Volanea, uses a stable idempotency key, and returns the provider response for downstream logging or a Slack confirmation step.
The function’s payload mapping is explicit:
| Slack form input | Volanea request field | Why it exists |
|---|---|---|
recipient_email | to[0].email | Destination address |
recipient_name | to[0].name | Optional recipient display name |
| fixed sender in code | from | Keeps sender identity controlled |
subject | subject | Subject approved in Slack |
customer_name and message | html and text | Customer-readable content |
case_id | Idempotency-Key and tags | Prevents duplicates and aids reporting |
Create functions/send_volanea_email.ts:
import { DefineFunction, Schema, SlackFunction } from "deno-slack-sdk/mod.ts";
export const SendVolaneaEmailDefinition = DefineFunction({
callback_id: "send_volanea_email",
title: "Send customer email with Volanea",
description: "Sends an approved transactional email from a Slack workflow.",
source_file: "functions/send_volanea_email.ts",
input_parameters: {
properties: {
recipient_email: {
type: Schema.types.string,
description: "Customer email address",
},
recipient_name: {
type: Schema.types.string,
description: "Customer name, if known",
},
customer_name: {
type: Schema.types.string,
description: "Name used in the message greeting",
},
subject: {
type: Schema.types.string,
description: "Approved email subject",
},
message: {
type: Schema.types.string,
description: "Customer-facing message body",
},
case_id: {
type: Schema.types.string,
description: "Stable ticket, incident, or CRM identifier",
},
},
required: ["recipient_email", "subject", "message", "case_id"],
},
output_parameters: {
properties: {
email_id: { type: Schema.types.string },
status: { type: Schema.types.string },
},
required: ["status"],
},
});
function escapeHtml(value: string): string {
return value
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll('"', """)
.replaceAll("'", "'");
}
export default SlackFunction(
SendVolaneaEmailDefinition,
async ({ inputs, env }) => {
const apiKey = env["VOLANEA_API_KEY"];
if (!apiKey) {
return {
error: "VOLANEA_API_KEY is not configured in the Slack app environment.",
};
}
const customerName = inputs.customer_name?.trim() || "there";
const safeName = escapeHtml(customerName);
const safeMessage = escapeHtml(inputs.message).replaceAll("\n", "<br>");
const volaneaPayload = {
from: "Support Team <support@example.com>",
to: [
{
email: inputs.recipient_email.trim(),
...(inputs.recipient_name?.trim()
? { name: inputs.recipient_name.trim() }
: {}),
},
],
subject: inputs.subject.trim(),
html: `
<p>Hi ${safeName},</p>
<p>${safeMessage}</p>
<p>— Support Team</p>
`,
text: `Hi ${customerName},\n\n${inputs.message}\n\n— Support Team`,
tags: ["slack-workflow", `case-${inputs.case_id.trim()}`],
};
const response = await fetch("https://api.volanea.com/v1/send", {
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
"Idempotency-Key": `slack-case-${inputs.case_id.trim()}`,
},
body: JSON.stringify(volaneaPayload),
});
const data = await response.json().catch(() => ({}));
if (!response.ok) {
console.error("Volanea send failed", {
status: response.status,
data,
case_id: inputs.case_id,
});
return {
error: `Volanea rejected the email request with HTTP ${response.status}.`,
};
}
return {
outputs: {
email_id: data.id ?? data.emailId ?? "accepted",
status: "accepted",
},
};
},
);
Replace support@example.com with an address at a domain you have verified in Volanea. Do not replace it with a Slack user’s personal address. Customer-facing operational email should use a stable, authenticated identity with a monitored reply path.
The Volanea call uses a bearer token in the Authorization header, JSON request content, and an Idempotency-Key header. The send endpoint accepts transactional message fields including a sender, recipient list, subject, HTML body, and text fallback. Volanea supports idempotency keys so a retry can refer to the same logical send instead of creating a second customer email.
Connect the function to a Slack workflow form
You can expose the custom function as a Workflow Builder step or use it inside a coded workflow. For a team that wants business users to maintain the form, the usual arrangement is to make the function available as a custom step, then build the workflow in Workflow Builder.
The workflow should use these steps in order:
- Start from a link trigger accessible only to the approved Slack users or group.
- Add a form step with the email fields described above.
- Add the custom step named Send customer email with Volanea.
- Map every form field to the matching custom-function input.
- Optionally add a Slack message step that confirms the email was accepted and includes the ticket or case ID.
The form submission is what moves the workflow from data collection to the email-send action. In the mapping screen, map the form outputs—not the display labels—to the custom function’s inputs. Labels can change for readability; the internal names should remain stable because they are part of the contract between the workflow and the code.
Keep the form intentionally constrained
A Slack form can create a false sense that it is a general email composer. It should not be one. Make it a controlled operational workflow instead.
Good controls include:
- Make the recipient email required.
- Use a short text field or selected template type for the subject instead of unlimited ad hoc subjects where possible.
- Make a CRM, ticket, or incident ID required.
- Limit workflow access to the support, operations, or incident-response group.
- Keep the sender address fixed in code.
- Use a confirmation step for especially sensitive notifications.
- Avoid collecting passwords, payment details, health data, or other sensitive content in a broadly accessible Slack form.
A form that sends a customer notification should be auditable. The stable case_id gives you a useful record in Slack, in your internal system, and in Volanea message tags. If the message has legal, financial, or account-security consequences, consider adding an approval step or move the final send decision to the authoritative backend system.
Store the Volanea API key on Slack’s server side
The Volanea secret key must be held by the Slack app environment, not passed through a form, saved in the workflow description, committed to the repository, or placed in a client-visible configuration file.
For a Slack-hosted Deno workflow app, Slack supports environment variables that custom functions can read through the env context. During local development, use a local .env file that is excluded from source control. For deployed apps, set the variable through the Slack CLI so it is stored for the deployed app environment.
Use this command while working in the Slack app project:
slack env set VOLANEA_API_KEY "sk_your_live_volanea_key"
Then read it only in the custom function:
export default SlackFunction(
SendVolaneaEmailDefinition,
async ({ env }) => {
const apiKey = env["VOLANEA_API_KEY"];
// Use only in server-side fetch() calls.
},
);
Never put that key in a Slack modal, Block Kit payload, browser app, mobile app, a code snippet shared in a channel, or an unprotected incoming webhook URL. A Volanea sk_ key is a secret key with broad project access; anyone who obtains it could potentially send email through your project or access other protected API operations.
Use least privilege operationally even when the provider key itself is project-scoped. Create separate Volanea projects or keys for development and production. Use sk_test_ in development. Restrict who can deploy Slack workflow changes. Rotate the key when a developer leaves, a repository exposure is suspected, or the workflow ownership changes.
For more endpoint details, send fields, and authentication behavior, use the email API reference and setup guides while building the integration.
Use idempotency to stop duplicate customer emails
The most important reliability decision in this integration is the idempotency key. Slack workflow execution, network failures, and transient provider errors can produce retries. If a caller makes a second request after the first request was accepted but before the caller received the response, the recipient can receive two nearly identical emails unless the downstream API recognizes both requests as the same logical action.
Do not create a random UUID each time the function runs if your intent is to prevent duplicate sends across retries. A new random value tells Volanea that every attempt is a new send.
Instead, derive the Idempotency-Key from the stable business event. In this example:
"Idempotency-Key": `slack-case-${inputs.case_id.trim()}`
For a simple workflow where each case ID should result in only one message, that key is sufficient. But if one case can legitimately produce multiple message types, make the key more specific:
"Idempotency-Key": `slack-case-${caseId}-access-approved-v1`
If a case can send multiple separate updates of the same kind, include a stable sequence or event identifier generated by the system of record:
"Idempotency-Key": `slack-case-${caseId}-update-${eventId}`
The key should describe the business action, not the HTTP attempt. A retry of the same approved workflow execution must use the same value. A genuinely new notification must use a different value.
Add a system-of-record guard for high-value sends
Idempotency at the email API layer is important, but it is not a substitute for business-level authorization. For password resets, account closures, financial notices, compliance alerts, or contract communications, Slack should initiate a request to your backend rather than directly create the customer email.
In that model, the Slack custom function calls your internal API. Your backend validates the Slack requester, looks up the ticket or account, checks approval state, records a durable send job with a uniqueness constraint, and then calls Volanea. This adds engineering work, but it gives you centralized audit history, policy enforcement, better retry control, and a way to use authoritative customer data instead of manually entered form fields.
When this breaks
Every integration eventually fails at a boundary: the user submits incomplete data, a function exceeds a platform limit, an HTTP response is lost, or the provider rejects the email. Design for those specific failure modes rather than treating “Slack sent it” as proof of delivery.
Slack retries can cause duplicate sends
A function may be retried or manually re-run after an uncertain failure. The risky sequence is straightforward: Volanea accepts the email, but Slack does not receive the HTTP response due to a timeout or network interruption. Slack or an operator tries again, and without a stable idempotency key, the recipient gets another email.
Mitigation: use the same Idempotency-Key for the same ticket or event. Do not generate a random key inside a retry path. Also show the case ID in your Slack success message so an operator can check whether the same workflow was already completed before trying again.
Function timeouts and slow downstream calls
Slack documents execution time limits for functions. A deployed function has a 60-second timeout, while certain interactive contexts have a shorter limit. Volanea’s send request should normally finish far below that, but your function can still become slow if it performs multiple lookups, waits on another service, retries too aggressively, or generates complex content.
Mitigation: keep the Slack function focused on one send. Do not poll delivery status inside the workflow execution. Treat a successful Volanea API response as acceptance for processing, not a final inbox-placement result. Use Volanea delivery events or your message logs for later delivery monitoring.
Missing form fields or changed workflow mappings
Slack passes only the fields the workflow actually supplies. A field can be blank because it was optional, renamed in the form, deleted from the workflow, or incorrectly mapped to the custom step. Slack plan capabilities and workspace administration can also affect which workflow and developer features are available.
Mitigation: define required fields in the custom function schema and validate values again in code. Keep a stable internal field name even if you change its visible form label. Log the case ID and a safe summary of validation errors, but never log the Volanea API key or sensitive message contents.
Invalid, suppressed, or unverified email destinations
A syntactically plausible address can still be unsuitable for delivery. It may be misspelled, belong to a recipient who unsubscribed from relevant mail, have previously bounced, or be blocked by suppression controls. The sender can also be rejected if you configured an address outside a verified domain.
Mitigation: use an approved sender address, verify your sending domain, and make the Slack form responsible for gathering only the destination and business context. If your process accepts addresses typed by staff, consider checking the address before the message is approved with the free address verification tool. For high-volume or critical flows, make the CRM or product database the source of the email address rather than Slack free text.
HTML breaks because users paste arbitrary content
Slack messages frequently include angle brackets, ampersands, emoji, pasted links, Markdown-like formatting, and line breaks. If you interpolate those values directly into HTML, an innocent phrase can create malformed markup, and untrusted input can become an HTML injection problem.
Mitigation: HTML-escape all values inserted into HTML, as the sample escapeHtml() helper does. Use a fixed email layout and convert only line breaks after escaping. If you need rich formatting, convert a constrained input format to safe HTML in a controlled backend service rather than treating Slack text as ready-to-send markup.
Slack access control is too broad
A workflow link can become an unofficial “email anyone” tool if it is shared too widely. That creates operational, privacy, and brand-risk problems even if the technical integration works perfectly.
Mitigation: set access controls for the trigger and custom function, restrict deployment rights, and periodically review who can invoke the workflow. For sensitive sends, include an approval stage or require a ticket identifier that is checked against an internal system before Volanea is called.
Test the full path before enabling live sends
Testing should prove more than “the function returned accepted.” Test the exact user interaction, payload mapping, delivery request, and operational recovery behavior.
Start with a Volanea test key and a dedicated Slack test workspace or restricted test workflow. Submit values that exercise normal and unpleasant cases: apostrophes in names, line breaks, missing optional names, a long message, a malformed recipient address, and a repeated case ID.
Use this acceptance checklist:
- Start the link trigger as an authorized user and confirm the form opens.
- Submit a valid test address and verify the custom function receives all expected
inputs. - Confirm that
recipient_emailbecomesto[0].email,subjectbecomessubject, and the message appears in bothhtmlandtext. - Verify that the sender is the fixed verified address, not a field chosen by the Slack user.
- Submit the same case ID twice and confirm the idempotency design prevents an unintended second send.
- Remove or corrupt
VOLANEA_API_KEYin a non-production test environment and confirm the function fails without exposing the secret. - Try content containing
<,>,&, quotes, and multiple lines; verify the delivered HTML remains readable. - Switch to a live key only after reviewing the sender identity, recipient access rules, and operational runbook.
Document what “success” means for your team. The Slack workflow can confirm that Volanea accepted the request. That is not the same as proving the recipient opened the message or that it landed in the primary inbox. Those are later delivery and engagement concerns that should be observed through Volanea’s email logs, events, and metrics.
Direct custom function versus Zapier or Make middleware
A Slack-hosted custom function is the cleanest direct approach when you can develop a Slack workflow app, your workspace plan supports custom workflow steps, and a small TypeScript function is acceptable. The key stays in Slack’s managed environment, field mapping is explicit in source code, and there is no separate automation vendor between Slack and Volanea.
A middleware route with Zapier or Make can be appropriate when the organization already uses one of those products and needs visual routing, approvals, branching, spreadsheet lookups, or integrations that are impractical to build immediately. In that design, Slack supplies the trigger or form data to the automation platform, and the automation platform calls an internal webhook or Volanea.
The trade-off is control. Middleware introduces another credential store, another retry policy, another execution log, and another point where a duplicate may be created. If you choose that path, keep the same principles: store the Volanea secret only in the automation platform’s protected credential store, use a stable idempotency key, validate fields before sending, and avoid putting customer-sensitive content in loosely controlled automation history.
For a simple, developer-owned operation, the direct custom-function route is usually easier to reason about. For a multi-app business process maintained by non-developers, middleware can be useful—but it should still call a controlled backend for high-risk email categories.
Operational design patterns that scale
The first version of this integration can send a one-off support update. The durable version becomes a small internal communication system. The difference is not an additional API call; it is deciding where state, templates, permissions, and evidence live.
Use fixed templates for recurring notices
If your team repeatedly sends the same type of notification, do not rely on an open-ended Slack message field. Use a choice field such as notification_type, then select a fixed subject and message layout in code. Inputs can supply only the variable content: account name, scheduled time, action URL, or incident number.
That approach prevents off-brand phrasing, reduces accidental omissions, and makes approval easier. It also allows you to version message types. An idempotency key can include the version, such as incident-247-customer-update-v2, when a new workflow should be considered distinct from a previous notification.
Separate operational tags from customer content
Tags are useful for sorting and reporting, but they should be safe internal labels. A case ID is normally suitable. Do not put message text, personal details, authentication tokens, or confidential incident notes into tags. The same principle applies to logs produced by the Slack function.
Send from the system of record when state matters
Slack is excellent for initiating an approved action. It is not necessarily the source of truth for a customer’s current email address, entitlement, payment state, or account permissions. For notifications tied to live customer data, Slack should send a case ID to your backend, and the backend should retrieve the current record before constructing the email.
This protects against copied stale addresses and gives the backend a chance to reject impossible or unauthorized transitions. It also makes it possible to record the Volanea message identifier next to the customer event, which helps support staff answer “was this actually sent?” without hunting through several systems.
Conclusion
To send email from Slack with Volanea, do not look for a nonexistent native Slack app or try to expose a bearer token in a no-code webhook field. Use a Slack link-triggered workflow, collect the approved details in a form, and pass its submitted fields to a Slack custom function. That function reads VOLANEA_API_KEY from Slack’s server-side environment, maps the fields to POST /v1/send, and uses a business-event idempotency key to make retries safe.
The direct integration is intentionally small, but its reliability depends on the surrounding decisions: a verified sender, controlled workflow access, explicit field mapping, safe HTML handling, stable idempotency, and clear treatment of “accepted for delivery” versus “delivered.” Start with a constrained internal workflow, test it with a Volanea test key, and move sensitive or high-impact sends behind your own backend validation layer.
FAQ
Does Volanea have a native Slack app or Slack Marketplace listing?
No. Volanea does not provide a native Slack app, Marketplace listing, or one-click Slack plugin for this use case. The direct approach is a Slack workflow with a custom function that calls Volanea’s REST API.
What starts the email send in Slack?
In this guide, a Slack link trigger starts the workflow. The workflow opens a form, and the user’s form submission supplies the values that are passed to the Volanea custom function.
Where should the Volanea API key be stored?
Store it as a Slack app environment variable, such as VOLANEA_API_KEY, set with slack env set. Read it only through the custom function’s env context. Never place an sk_ key in a form, frontend configuration, public webhook URL, or source repository.
How do I stop Slack retries from sending duplicate emails?
Use Volanea’s Idempotency-Key header and derive its value from a stable business identifier such as a ticket, incident, or CRM event ID. A retry of the same logical email must reuse the same key.
Can I use Zapier or Make instead?
Yes, as middleware, especially if your team already operates those tools. However, they add another place for credentials, logs, retries, and duplicate-send risk. Keep the Volanea key in protected credentials, use idempotency, and route sensitive sends through your own backend.