Send email from Convex without forcing an SMTP-shaped workflow into a fetch-native backend. Volanea gives Convex developers a straightforward REST path for transactional messages, campaigns, deliverability controls, and the operational records needed to understand what happened after a user presses “send.”

Convex changes the usual backend assumptions in useful ways. Its default runtime is always ready rather than relying on traditional serverless cold starts, its database functions are deliberately transactional, and its actions are the boundary for side effects such as external HTTP calls. That is excellent architecture—but it means email belongs in the right place. A password-reset message, invitation, receipt, verification code, or account alert should be treated as an external delivery operation, not as an incidental line of code inside a database write.

Volanea fits that model with a JSON-over-HTTPS email API. Keep the key in Convex’s deployment environment, use an action to call the send endpoint with fetch, and let your mutation schedule the action only after the underlying state change succeeds. The result is a sending flow that respects Convex’s execution model and gives your application a durable foundation for email.

Why Convex developers run into email-sending friction

Convex makes it easy to build reactive application behavior: a client calls a mutation, data changes atomically, subscriptions update, and the UI follows. Email is different because it crosses your application boundary. It depends on a third-party API, DNS-authenticated sender identity, recipient mailbox providers, and asynchronous delivery events.

The first point of friction is architectural. Convex queries and mutations do not make external fetch calls. That restriction is intentional: it preserves the transactional and reactive properties that make Convex applications predictable. Email therefore needs an action, which is the Convex function type designed to communicate with external services.

The second point is runtime compatibility. A lot of historical Node email examples assume an SMTP library, a TCP connection, socket behavior, and a long-lived server process. Convex’s default runtime is closer to a modern web-worker environment and exposes fetch in actions. For a Convex integration, an HTTPS email API is the natural default: it matches the platform’s network primitive, avoids pulling in a transport designed around raw SMTP connections, and keeps the implementation small.

The third point is sequencing. If a mutation both creates a user and sends a welcome email directly, a failure mode becomes ambiguous. Did the user record commit? Did the provider accept the email? Did a retry create two users, send two welcomes, or both? Convex gives you a better option: perform the durable state change in a mutation, schedule an internal action from that mutation, and send from the action. Scheduling from a mutation is atomic with that mutation, so a successful write guarantees the follow-up work is queued.

Finally, secrets often confuse teams moving between local frontend tooling and Convex backend functions. A frontend framework may load variables from .env.local, but Convex backend functions use environment variables configured on the Convex deployment. Your Volanea key must never be bundled into browser code, included in a client-readable NEXT_PUBLIC_ or VITE_ variable, or stored in a database table that ordinary application code can query.

The email problem is not hard because sending JSON is complicated. It is hard because a production-quality email flow has to preserve application consistency, protect credentials, prevent duplicates, and support delivery troubleshooting. Convex and Volanea give you the right primitives for that job.

The Convex-native pattern: mutation, scheduler, internal action

The most reliable pattern separates your email workflow into three responsibilities:

  1. A mutation changes application state—for example, it creates an invitation record, records an order, or creates a password-reset request.
  2. The mutation schedules an internal action with the durable record ID or a narrow set of validated arguments.
  3. The internal action calls Volanea over HTTPS, records the provider response, and surfaces or retries failures according to the importance of the message.

This is not needless ceremony. It prevents an external side effect from being mixed into the transaction that creates the business event. A user who receives an invitation should have a real invitation record behind the link. An invoice email should correspond to an invoice that was actually finalized. A reset link should point to a token that was persisted before a mail provider ever saw the request.

Why not send directly from a mutation?

A mutation is where you want strong data guarantees. If it could call an external endpoint in the middle of a transaction, your application would have to handle awkward partial-success cases: the provider might accept the email while the mutation later fails, or a mutation retry might repeat the provider call.

Convex avoids that class of problem by keeping external calls in actions. Use the boundary rather than fighting it. The mutation can atomically save the event and schedule the action, while the action handles the network call as a separate, observable operation.

Why make the action internal?

Most application emails are initiated by trusted server-side logic. Your browser client should call a public mutation such as inviteMember, requestPasswordReset, or completeCheckout; it should not call a generic public function that accepts arbitrary recipients, HTML, and sender addresses.

An internalAction is not exposed to the client. That keeps the email API key on the server and narrows the places from which sending can be initiated. It also makes code review easier: the public mutation governs whether an email should exist, while the internal action governs how it is dispatched.

A short Convex + Volanea example

This example sends a welcome message from an internal Convex action. It uses Volanea’s POST /v1/send endpoint, HTTPS fetch, a server-side API key, both HTML and plain-text content, and an idempotency key tied to the business event.

// convex/email.ts
import { internalAction } from "./_generated/server";
import { v } from "convex/values";

export const sendWelcome = internalAction({
  args: {
    userId: v.id("users"),
    email: v.string(),
    firstName: v.string(),
  },
  handler: async (_, args) => {
    const response = await fetch("https://api.volanea.com/v1/send", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.VOLANEA_API_KEY}`,
        "Content-Type": "application/json",
        "Idempotency-Key": `welcome:${args.userId}`,
      },
      body: JSON.stringify({
        from: "Acme <hello@mail.acme.example>",
        to: args.email,
        subject: `Welcome, ${args.firstName}`,
        html: `<h1>Welcome, ${args.firstName}</h1><p>Your account is ready.</p>`,
        text: `Welcome, ${args.firstName}. Your account is ready.`,
      }),
    });

    if (!response.ok) {
      throw new Error(`Volanea send failed: ${response.status}`);
    }

    return await response.json();
  },
});

Keep the sample short in production code too. Put complex rendering in a dedicated template module or a Volanea template, do not interpolate untrusted HTML into a string, and use the action’s inputs only after the public workflow has validated authorization and intent.

For endpoint details, message fields, templates, batch sends, and response handling, consult the email API reference and setup guides.

Use REST instead of SMTP in Convex

SMTP is a protocol for mail transfer, and it remains useful in environments that are built around it. But that does not mean it is the best integration point for every runtime. In Convex, a REST email API aligns with the runtime you already have.

An action can make an outbound HTTPS request with fetch. That means you can send JSON to Volanea without adding an SMTP transport package, managing connection settings, adapting Node-specific networking assumptions, or translating errors from a socket-centric library into your application’s reliability model.

What REST changes operationally

A REST request gives your application normal HTTP semantics:

  • A clear request body that can include recipients, content, metadata, and a sender.
  • Standard status handling for accepted or rejected requests.
  • A place to include an idempotency header for retry safety.
  • A provider response that can be recorded alongside your Convex email record.
  • A transport that works naturally with serverless-style and worker-like JavaScript runtimes.

That last point matters more than it may initially appear. Email sending is not a place where you want runtime-specific infrastructure to become your application’s hidden dependency. If your delivery function can be expressed as one authenticated HTTPS request, the same basic design travels well across Convex actions, HTTP actions, workers, background jobs, and future services.

Connection reuse is not your responsibility

On a traditional long-running Node server, teams sometimes tune SMTP connection pools or reuse HTTP clients to reduce connection overhead. In a managed runtime, you should avoid making correctness depend on a process living for a particular duration. An action may run independently of the action before it, and your application should work whether a connection is reused or not.

With a REST API, your code expresses the business operation—send this message—rather than managing the lifecycle of a mail connection. That keeps your failure model simpler. You still need sensible timeouts and retry rules in a mature architecture, but you do not need to build a connection-pool strategy around assumptions that do not belong to Convex.

Keep SMTP available for systems that need it

Volanea supports SMTP as well as REST sending, which can be useful when a vendor integration, legacy system, or framework plugin only speaks SMTP. The point is not that SMTP is obsolete. The point is that a Convex action is already fluent in HTTPS, so REST is typically the cleanest first choice for application-originated email.

Configure secrets separately for development and production

A correct email integration can still fail at deployment if the secret-management model is wrong. Convex environment variables are set per deployment. Your development deployment and production deployment can use the same variable name with different values, which is exactly what email needs.

Use a backend-only variable such as VOLANEA_API_KEY. In development, use an appropriate test or non-production credential when available and send only to addresses your team controls. In production, set the production key and use your authenticated production sending domain.

Do not rely on a frontend .env file for backend secrets

Convex’s CLI creates local project configuration such as CONVEX_DEPLOYMENT, and frontend frameworks may consume .env.local values for browser builds. Neither fact means a secret sitting in that file is automatically available to Convex backend functions.

Set the key on the Convex deployment with the dashboard or CLI. For example, npx convex env set VOLANEA_API_KEY sets a variable on the current deployment, while the production option targets the production deployment. Your backend function then reads process.env.VOLANEA_API_KEY at execution time.

This distinction prevents two costly mistakes:

  • A developer believes local testing succeeded because their frontend build could see a key, then discovers the Convex action cannot.
  • A secret gets prefixed for browser exposure and is accidentally shipped to every visitor.

Treat development sending as a real environment

Do not make development email an afterthought. It should be intentionally safer than production, not merely less likely to be noticed. Use a separate sender subdomain, a test key where your provider supports one, or an allowlist that redirects non-team recipients to a safe inbox.

A good development policy answers these questions before someone runs a bulk fixture or loops through a test workflow:

  • Which addresses may receive messages from the dev deployment?
  • Which verified sender domain appears in development?
  • Can developers inspect provider responses and Convex logs without exposing recipient data broadly?
  • How will preview deployments avoid sending live customer email?
  • What happens if a production key is accidentally configured in a non-production deployment?

The cleanest answer is isolation: separate credentials, separate sender identity where appropriate, and a deliberate deployment-specific configuration review.

Make sends durable with idempotency keys

Email is a side effect. A network timeout can leave you uncertain whether Volanea received the request. Retrying blindly can send the same receipt twice; refusing to retry can leave a user without a verification message. An idempotency key gives the provider a stable identity for the logical send so that a repeated request can be recognized as the same operation.

Volanea’s send endpoint supports the Idempotency-Key header. Use it whenever a message is tied to a durable business event.

Build keys from business identifiers, not timestamps

The welcome example uses welcome:${userId} because the product intends to send at most one welcome email for that user. A receipt could use receipt:${orderId}. An invitation could use invite:${invitationId}. A password reset requires more care: users may legitimately request multiple reset emails, so key it by the unique reset-request record or token ID, not merely by user ID.

Good idempotency keys are:

  • Stable across retries of the same intended message.
  • Unique across distinct business events.
  • Derived from values your application persists or can deterministically reconstruct.
  • Meaningful enough to debug without putting sensitive recipient data into logs.

Avoid Date.now(), random values generated for every attempt, or request IDs that change when a user reloads the page. Those values defeat duplicate protection because a retry no longer looks like the original operation.

Record a local email job or delivery record

For important flows, create an emailMessages or emailJobs table in Convex. Store the event type, recipient reference, logical message key, intended sender, current status, attempt count, provider message ID if returned, and timestamps.

Do not store more recipient data or message content than you need. A reference to a user record plus a redacted subject or template name is often more appropriate than saving every rendered message body. The goal is operational traceability: when support asks why a customer did not receive an invoice, you need to find the application event, the send attempt, the provider response, and later delivery events.

Decide what to retry

A retry policy should distinguish failures rather than treating every non-success result the same way. Validation errors—an invalid sender, malformed payload, or unsupported field—need a code fix or data correction. Authentication errors need a credential or deployment-config fix. A temporary upstream failure may be retried with backoff using the same idempotency key.

For high-value messages such as account recovery, billing notices, and security alerts, record the failure and create an explicit recovery path. That may be a scheduled retry, an internal support alert, or an in-product notice that lets the user request a new message. The right choice depends on urgency, expected volume, and whether repeating the communication is safe.

Deliverability starts before the action runs

A successful API response means Volanea accepted your request. It does not mean a recipient saw the message in their inbox. Mailbox placement depends on sender authentication, content, recipient engagement, complaint rates, bounce handling, link reputation, and the sending behavior of your domain over time.

Convex does not create special deliverability penalties. The deliverability requirements are the same regardless of whether the send originates from a monolith, a queue worker, or a Convex action. What Convex does affect is how cleanly you can model the events and guardrails that protect your sending reputation.

Authenticate the domain you actually send from

Use a sending domain or subdomain you control, and complete the DNS records Volanea provides for domain authentication. In practical terms, that means configuring the authentication records required for your setup—commonly SPF, DKIM, and DMARC-related policy—rather than sending long-term from an unverified address.

A dedicated subdomain such as mail.example.com or notify.example.com can separate product mail from other mail streams. That is especially useful when transactional email and promotional campaigns have different audiences, cadence, and risk profiles. It lets your team reason more clearly about what kind of message a recipient received and which stream needs attention when metrics change.

Do not copy DNS values from another provider’s documentation. Domain-verification records are provider-specific. Add the exact hostnames and values presented for your Volanea domain configuration, then verify the domain before switching production traffic.

Include a plain-text alternative

Provide both html and text for transactional messages. The plain-text version helps recipients and clients that cannot or should not render HTML, and it forces a useful content review: can the message still be understood without visual layout, tracking pixels, or branded buttons?

For a password reset, the text version should plainly identify the app, explain why the user received the message, show the key action or URL where appropriate, explain expiry behavior, and tell the recipient what to do if they did not request it. The HTML version can improve readability, but it should not contain information that only exists in a graphic or button.

Separate transactional and marketing intent

A receipt, security alert, verification code, or requested reset is transactional. A product announcement, nurture series, feature promotion, or newsletter is marketing. The difference matters for content, unsubscribe expectations, consent, frequency, and your application’s data model.

Volanea’s API distinguishes campaign-oriented capabilities from direct application sending. Do not disguise promotional content as a transactional message just because the code path is convenient. Put marketing sends through the appropriate audience, consent, and unsubscribe workflow, and make the message type explicit when sending inline content.

Protect the reputation of your signup flow

The fastest way for a new application to create unnecessary email trouble is to send to poor-quality addresses at scale. Validate syntax in your UI, use a confirmation or verification flow where appropriate, and consider checking addresses before expensive or high-risk sequences. Volanea’s free email address verification tool can help evaluate an address before you add it to an important workflow.

That is not a substitute for consent or suppression handling. A technically deliverable address may still belong to someone who did not request your message. Deliverability is a product behavior as much as it is an infrastructure setting.

Build practical Convex workflows for common email events

The same core architecture works across application email. What changes is the business event, the idempotency scope, and the urgency of recovery.

Welcome and account-verification emails

Create the user or pending registration record in a mutation. Generate a verification record with an expiry time. Schedule an internal action after the mutation commits. The action reads the necessary trusted details, sends the message, and records the provider result.

The idempotency key should be tied to the verification record if users can request a new verification email. If your application creates a new verification token on every resend, every token is a distinct message event and deserves its own key.

Invitations

An invitation should be a first-class record, not a URL generated inside a component and emailed immediately. Persist the inviter, target email, organization or workspace, role, expiration, and status. Then schedule the send action with the invitation ID.

This makes resends, revocations, audit trails, and access control much easier. It also prevents a common bug: sending an invitation that the application later cannot validate because the corresponding database write did not succeed.

Receipts and order updates

Receipt sends should be triggered from the business state that declares an order finalized, paid, refunded, or shipped. Do not tie the email solely to a client-side success page, because users can close a tab, refresh, block scripts, or return later.

Use an order event ID in the idempotency key. Store the exact invoice or order reference used in the message. If multiple updates are legitimate—such as partial shipment and final delivery—give each update a distinct durable event record rather than attempting to infer uniqueness from copy text.

Password resets and login alerts

Security-related email has a different operational bar. Send it quickly, do not include unnecessary personal data, use short-lived tokens, and do not make the flow depend on a marketing contact profile being eligible for a campaign.

Build a clear recovery experience. If sending fails, the user should receive a truthful message in the product rather than a false promise that an email was delivered. If a reset request is rate-limited, explain the next step without disclosing whether an account exists at a particular address.

Observability: connect application events to email outcomes

A mature email integration has more than a console.log after fetch. You need enough information to diagnose where a message stopped without collecting unnecessary sensitive content.

At minimum, correlate these layers:

  1. The application event: user created, invitation issued, order paid, reset requested.
  2. The Convex send job: scheduled time, action execution, attempts, and local state.
  3. The Volanea submission result: HTTP result and provider message identifier when supplied.
  4. Delivery events: delivered, bounced, complained, unsubscribed, opened, or clicked where those signals are relevant and permitted for the message type.

Use webhooks deliberately

Email events happen after the send action returns. A mailbox may reject a message later; a recipient may complain days afterward; a user may unsubscribe from marketing mail long after their original contact record was created.

A Convex HTTP action can receive an external webhook, parse the request, verify it according to Volanea’s current webhook guidance, and use internal mutations to update your local delivery record. Keep the webhook handler focused: validate the request, deduplicate the event if needed, persist a minimal event record, and return promptly.

Do not make a webhook endpoint public without authentication or signature verification just because it is convenient during development. An attacker who can fabricate bounce or unsubscribe events can distort your data; an attacker who can trigger arbitrary internal workflows can do much worse.

Watch the metrics that change decisions

For transactional email, accepted requests and delivery success are more important than vanity engagement metrics. Watch hard bounces, complaints, suppression activity, and time-to-send for critical flows. For campaign mail, consent, unsubscribe rate, spam complaints, and audience quality deserve equal attention.

Operationally, ask questions that lead to an action:

  • Are reset emails failing only for one sender domain?
  • Did bounces rise after a new signup import?
  • Are invitation actions timing out, or is the provider rejecting the sender?
  • Did a template change remove the plain-text alternative?
  • Are customers reporting missing receipts because the send was never scheduled, never accepted, or later bounced?

When your Convex records and Volanea events share a logical message key or provider ID, these questions become investigations instead of guesses.

Templates, personalization, and safe content boundaries

You can send inline HTML for simple messages, especially early in an application. But once several workflows share branding and content, templates reduce drift. A sender name changes, footer policy changes, or support URL update should not require editing ten separate action files.

Volanea supports reusable templates addressed by template ID. That can be a better fit when a message is a stable product artifact and the action only needs to supply approved variables. Keep template changes under review, especially for security emails, billing messages, and any flow where an incorrect variable could expose data to the wrong person.

Do not render untrusted HTML

Email HTML is still HTML. Treat user-generated names, organization titles, comments, and other interpolated values as untrusted data. Escape output in your rendering layer. Avoid placing arbitrary submitted content in raw HTML fields. A message does not become harmless merely because it is delivered to an inbox instead of rendered in your app.

Prefer a small set of typed template variables over passing an entire Convex document into a renderer. For example, an invitation template might need inviteUrl, workspaceName, inviterName, and expiresAt. It does not need every field on a user, organization, or permissions document.

Keep personalization proportional to the message

Transactional messages should be useful first. A receipt needs the order reference and a support route. An invite needs the workspace and call to action. A reset message needs a secure link and clear expiration details. Adding unrelated promotional copy to these messages complicates consent and can damage trust.

For campaigns, use the audience and segmentation tools that match explicit marketing consent. For product mail, keep the action code aligned with the user event that caused the message. This separation makes both your codebase and recipient expectations clearer.

A production checklist for sending email from Convex

Before enabling live traffic, review the integration end to end. A single successful test message is helpful, but it does not prove your retries, deployment settings, DNS, or failure path are correct.

  • The Volanea API key exists only in Convex deployment environment variables.
  • Development, preview, and production deployments use intentionally different email configuration.
  • Public client functions cannot send arbitrary email or access the API key.
  • Mutations persist the business event before scheduling an internal send action.
  • The send action uses Volanea’s HTTPS REST endpoint through fetch.
  • Important messages use deterministic Idempotency-Key values.
  • The sending domain is verified with the exact DNS records supplied by Volanea.
  • Messages include both HTML and plain-text content, or use a reviewed template that does.
  • Transactional and promotional messages follow distinct consent and unsubscribe rules.
  • Failed sends are logged and recorded with enough context for support and recovery.
  • Webhook events are authenticated, deduplicated, and mapped to local message records.
  • Hard bounces, complaints, and unsubscribes influence future sending behavior.

This checklist is intentionally broader than a code snippet. Email delivery is a system. The action is only the place where your application starts the system.

Send email from Convex without adding email infrastructure debt

Convex gives you a clean way to separate database transactions from external side effects. Volanea gives you a REST email API that fits naturally inside that design. Together, they let you send a welcome email, receipt, alert, or campaign without turning a simple product event into an SMTP integration project.

Use a mutation to make the business change. Schedule an internal action once that change commits. Call Volanea through HTTPS with a protected key. Use deterministic idempotency keys. Authenticate your sender domain. Track the provider response and subsequent delivery events. That architecture stays understandable when you are sending one verification message a day and when you are operating a large product workflow with many message types.

The best email integration is not the one with the fewest lines of code on launch day. It is the one that still behaves predictably when a network request times out, a deployment changes, an address bounces, a user requests a resend, or a support engineer needs an answer quickly. Convex provides the durable workflow primitives; Volanea supplies the sending and deliverability layer.

FAQ

Can Convex send email directly from a mutation?

No. Convex mutations do not make external API calls. Use a mutation to write state and schedule an action, then call Volanea from the action with fetch.

Should I use SMTP or a REST API with Convex?

For most Convex applications, use Volanea’s REST API. Convex actions support fetch, so HTTPS JSON requests fit the default runtime cleanly. Use SMTP only when another integration specifically requires it.

Where should I store my Volanea API key in Convex?

Store it as a Convex deployment environment variable such as VOLANEA_API_KEY. Do not expose it in browser environment variables, client components, public actions, or frontend bundles.

How do I prevent duplicate emails when an action retries?

Pass a stable Idempotency-Key based on the durable business event, such as an order ID, invitation ID, or reset-request ID. Do not generate a new random key for every retry.

Does using Convex affect email deliverability?

Not inherently. Deliverability depends on sender-domain authentication, message content, recipient quality, bounce and complaint handling, consent, and sending behavior. Convex helps you implement reliable workflows around those requirements; Volanea handles the email delivery infrastructure.