Supabase makes it fast to ship authentication, a database, storage, and server-side logic in one place—but send email from Supabase and a few production realities arrive quickly. Your code runs in an edge/serverless environment, secrets must stay out of the browser, and traditional SMTP assumptions do not map cleanly to short-lived functions.
Volanea gives Supabase developers a straightforward path: call an HTTPS email API from an Edge Function, keep the API key in Supabase secrets, use deterministic retries, and send from an authenticated domain. It is a small integration at the code layer, but it removes a surprising amount of operational friction from password resets, invitations, receipts, alerts, and lifecycle email.
Why email feels different in a Supabase app
A Supabase project can handle most application events close to the data: a user signs up, a team owner creates an invite, a subscription changes, a support form is submitted, or a scheduled job finds an overdue invoice. Each event is an obvious candidate for an email.
The catch is that email is not simply another database query. It crosses a system boundary. Your application has to construct the message, authenticate to a sending service, survive network uncertainty, preserve user privacy, avoid duplicates, and protect the reputation of the domain recipients see in their inbox.
In a conventional long-running server, developers often reach for an SMTP library, hold connections open, and rely on process-level state. Supabase Edge Functions are intentionally a different runtime model. They are server-side TypeScript functions running on a Deno-compatible edge runtime, can experience cold starts, and are best designed as short-lived and idempotent request handlers.
That changes the practical question from “Which mailer package should I install?” to “What is the smallest reliable request my function can make?” For most Supabase projects, the answer is an HTTPS request to an email API.
The serverless constraints that matter
The important constraints are not theoretical. They affect how you structure the send path:
- Cold starts can happen. Email work should not depend on a warm process, a cached SMTP transport, or local in-memory state.
- Functions have execution and request-idle limits. Do not make a browser wait while an email provider performs delivery attempts; hand the message to the provider quickly and return a clear application response.
- A function may be invoked more than once. Retries, webhook redelivery, client reconnections, and ambiguous network failures are normal distributed-systems behavior.
- Supabase blocks outbound connections to SMTP ports 25 and 587. A traditional SMTP submission flow is not the right hosted Edge Functions integration.
- Secrets differ between local and hosted development. A key that works from a local
.envfile must also be configured as a hosted project secret before production sends can succeed.
Volanea’s REST API is designed for this model: your function makes an outbound HTTPS request rather than opening and managing a raw SMTP connection.
Use REST, not SMTP, in Supabase Edge Functions
SMTP remains useful in environments that own a persistent application process or where a framework has a mature mail transport abstraction. It is not the natural default for a Supabase Edge Function.
Hosted Supabase Edge Functions explicitly disallow outbound connections to ports 25 and 587. More broadly, SMTP introduces transport lifecycle work that serverless handlers do not need: TLS negotiation, connection setup, credential configuration, connection reuse decisions, and timeout behavior across a short-lived invocation.
An HTTPS API request is a better fit. fetch is available in the Deno-compatible runtime, uses standard web primitives, and keeps the integration dependency-light. Your application creates a JSON request, Volanea accepts the message for processing, and your function can continue with a small, predictable response path.
This is not just a convenience choice. It separates the responsibilities cleanly:
- Supabase owns the application event and authorization boundary. Your function decides whether a caller is allowed to trigger an email and which recipient should receive it.
- Volanea owns message processing and sending infrastructure. It checks sending configuration, processes the request, and dispatches the email through an email-delivery pipeline.
- Your database owns business state. It records whether an invitation was created, a receipt is due, or a verification action has been completed.
That division is easier to observe and easier to retry safely than a function that tries to do everything at once.
Send email from Supabase with one HTTPS call
A useful first implementation is an Edge Function that receives an already-authorized application request, reads the Volanea key from the environment, and makes a single POST request. The integration uses Volanea’s POST /v1/send endpoint at https://api.volanea.com.
Here is the core request pattern inside an Edge Function. Keep the message object aligned with the fields in the Volanea API reference and setup guides, especially as you add templates, variables, scheduling, or tracking preferences.
const response = await fetch("https://api.volanea.com/v1/send", {
method: "POST",
headers: {
Authorization: `Bearer ${Deno.env.get("VOLANEA_API_KEY")}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify(message),
})
The short snippet hides an important design decision: message should be constructed on the server, not accepted uncritically from the browser. A browser can request “send my invite,” but it should not be able to choose arbitrary recipient addresses, sender identities, HTML, or an unrestricted template ID.
For example, an invite function can accept a teamId and invitee email from an authenticated user, check that the requester has permission to invite members, create the invitation row, then build the email from the trusted values stored in your database. That protects the email endpoint from becoming an open relay and keeps auditability in your application.
A practical function boundary
A healthy Supabase email function usually has four phases:
- Authenticate and authorize. Validate the Supabase JWT or otherwise establish that the caller is permitted to take the action.
- Validate business data. Confirm the relevant team, order, account, or workflow record exists and is in the right state.
- Persist the application action. Create an invitation, record a requested receipt, or save an event row using an appropriate transaction or unique constraint.
- Hand off the email. Send to Volanea using a stable idempotency key tied to the logical action.
You may choose whether email handoff occurs before or after writing your core database record based on your workflow. The key is to make the decision explicit. For an account invitation, the database invitation is usually the source of truth; the email is a delivery channel for that invitation. If the send request has a transient failure, you should be able to retry it without creating a second invitation or sending a confusing duplicate message.
Keep Volanea credentials out of the browser
An email API key is a server credential. It can authorize real sending activity, so it belongs in Supabase Edge Function secrets—not in a frontend environment variable, browser bundle, mobile app, or public repository.
Supabase Edge Functions expose environment variables through Deno.env.get(). In local development, Supabase can load function-specific variables from supabase/functions/.env, or you can provide a selected environment file when serving a function. In hosted deployments, configure the production value as a Supabase project secret.
Use a dedicated name such as VOLANEA_API_KEY. Avoid names that begin with SUPABASE_, because that prefix is reserved by Supabase for its own environment variables.
Treat local, preview, and production as separate sending environments
The secret-management problem is not only about preventing exposure. It is also about preventing accidental production mail.
A practical setup separates environments like this:
- Local development: use a test key and a controlled recipient mailbox. Developers should be able to run functions locally without sending to real customers.
- Preview or staging: use a separate verified test subdomain, separate key, and a constrained recipient policy where possible.
- Production: use a production key, an authenticated sending domain or subdomain, and real event monitoring.
Do not rely on a comment in code saying “do not send in staging.” Make the environment itself enforce the distinction. Separate credentials and sender domains make mistakes much easier to contain.
You should also avoid logging secrets. When troubleshooting an Edge Function, log a request correlation ID, a database record ID, a Volanea response status, and non-sensitive provider message identifiers when available. Never log the API key, full authorization header, password-reset token, or raw HTML containing private user data.
Build for idempotency, not best-case networking
Sending an email is a side effect. Once an email has been accepted and delivered, it cannot be silently recalled from a recipient’s inbox. That is why safe retries matter.
Imagine this sequence: your function calls the email API, Volanea accepts the request, but a network interruption occurs before the function receives the response. Your application sees a timeout. If it blindly submits the same message again, the recipient could receive two invitations, two receipts, or two password-reset emails.
Volanea supports an Idempotency-Key header on sending requests. Generate one key per logical email action, then reuse that exact key if you retry the same action. Do not generate a fresh random key for each retry; that would make every retry appear to be a new send.
Use a stable business identifier
The code snippet uses crypto.randomUUID() to illustrate the header, but production logic often benefits from a stable application-level value. For an invitation, that could be derived from the invitation row ID. For a receipt, it could include the immutable invoice ID and a receipt version. For a password reset, it could be associated with the specific reset-token record rather than merely the user ID.
The rule is simple:
- Create one key for one intended message.
- Reuse that key for retries of that same message.
- Create a new key when you intentionally send a different message.
This small discipline is especially valuable with Supabase because events can originate from several places: a browser-triggered function invocation, a database webhook, a scheduled task, a queue consumer, or an external payment provider’s webhook. Every one of those sources can retry.
Do not confuse accepted with delivered
A successful API response means the provider accepted your request for processing. It is not the same as proof that the recipient opened the message, saw it in their inbox, or completed the action.
Your product should model the distinctions that matter. An invitation can be created, email requested, email accepted, accepted by the recipient, and expired. A receipt can be generated even if the recipient’s mailbox rejects it. A password reset request should remain privacy-preserving regardless of whether an account exists or whether a message is later delivered.
That state model prevents a common support problem: treating a synchronous application response as a guarantee of inbox placement.
Deliverability starts before the Edge Function runs
Serverless changes the transport mechanics, but it does not change the fundamentals of email deliverability. Recipients and mailbox providers evaluate the identity and behavior of the sender, not whether your code ran in a container, virtual machine, or edge isolate.
The most important deliverability work is therefore outside your TypeScript handler: authenticate the sending domain, use a consistent and recognizable sender identity, send messages recipients expect, and react appropriately to bounces, complaints, and unsubscribes.
Volanea requires a sender on a verified domain for production sending. That matters because domain authentication lets receiving systems verify that the email service is authorized to send for your domain.
Use a sending subdomain deliberately
Many product teams choose a subdomain such as notify.example.com or mail.example.com for application email. This can create a cleaner operational boundary between product mail and ordinary person-to-person corporate mail, while preserving a sender identity users recognize.
There is no magic subdomain that guarantees inbox placement. The value is operational clarity: you can define the sending purpose, authentication records, reputation monitoring, and change process around a specific stream of mail.
For example, a product might use:
accounts@notify.example.comfor authentication and account noticesupdates@notify.example.comfor customer-requested product updatesreceipts@notify.example.comfor invoices and payment confirmations
Keep the purpose behind each sender clear. A recipient who receives a security alert from one address and a promotional campaign from another has a better chance of understanding why each message arrived.
Separate transactional and promotional intent
A password-reset message, login alert, invoice, invitation, and order confirmation are transactional communications. Their content should stay focused on the action the user initiated or the service relationship they already have.
Marketing email has different consent, frequency, preference, and unsubscribe expectations. Mixing promotional content into a password reset or an account-verification email can harm trust and create unnecessary deliverability risk.
Volanea supports both transactional sending and campaigns, but the application architecture should still respect the difference. Treat transactional sends as event-driven, personalized operational messages. Treat campaign sends as a separately governed workflow with audience criteria, consent handling, and frequency controls.
Keep Edge Functions fast and intentionally small
Supabase Edge Functions are a strong place to orchestrate an email event because they are close to Auth, Postgres, Storage, and application APIs. They are not the place to perform every possible piece of work synchronously.
A user who clicks “Send invite” should not wait while your function renders large documents, performs multiple sequential third-party calls, creates complex reports, and retries an email delivery pipeline. The function should validate the request, commit essential state, hand off the message, and respond.
This approach helps with cold starts and time budgets. It also creates a cleaner user experience: the application can tell the user that an invitation was created, while any delayed retry or delivery event is handled through an operational workflow rather than a spinner that never resolves.
Choose the right trigger for each email
Not every email must originate from the same type of function invocation.
Direct user actions work well for an authenticated Edge Function:
- Team invitations
- Contact-form notifications
- Export-ready messages
- Manual resend actions
- Account preference confirmations
System-generated events may work better from a backend trigger, database webhook, scheduled function, or queue-like workflow:
- Payment receipts after a verified payment event
- Daily digest emails
- Expiring-trial reminders
- Failed-payment notices
- Follow-up notifications for incomplete onboarding
The key is that the trigger should be durable enough for the business event. If an email depends on an external payment webhook, do not make the browser the source of truth for that email. If a daily digest needs to evaluate many users, do not attempt to loop through an unbounded audience during one user request.
Design email endpoints like security-sensitive APIs
A function that can send email can be abused for spam, phishing, enumeration, or unnecessary cost if it lacks controls. The fact that it is deployed in your Supabase project does not automatically make it safe.
Start with authorization. An authenticated user should only be able to trigger emails related to records they are allowed to access. Use Supabase Row Level Security and function-side checks as appropriate, especially when the function uses elevated database credentials for administrative operations.
Then add input and rate controls. Email addresses should be validated before they are written or used, but validation is not permission. A legitimate-looking address does not mean the current caller should be allowed to email it.
A practical security checklist
Before exposing an email-triggering Edge Function to your frontend, verify the following:
- The Volanea API key is only available inside the function runtime.
- The caller is authenticated unless the use case is intentionally public.
- Authorization is checked against the relevant database record, team, or account.
- The sender address is chosen by trusted server-side code.
- Template selection is allowlisted rather than supplied freely by the browser.
- Recipient rules match the use case; do not let a normal user notify arbitrary addresses unless that is the product feature.
- Inputs are normalized and validated before use.
- A rate limit exists for public or abuse-prone flows such as contact forms and verification requests.
- Retries reuse an idempotency key.
- Logs avoid keys, tokens, and sensitive message content.
For signup and password-reset flows, consider privacy as well as security. A response such as “If an account exists, we sent instructions” prevents the endpoint from revealing whether a specific address is registered.
Make templates part of your application contract
It is tempting to compose HTML strings inside every Edge Function. That works for a single prototype message, but it becomes difficult to maintain once your product has invitations, notices, receipts, security alerts, team changes, and onboarding messages.
Templates create a useful boundary between application events and email presentation. Your function supplies the trusted facts: recipient name, organization name, action URL, expiry time, and record identifier. The template defines the layout, visual system, fallback text, and messaging hierarchy.
Volanea templates support variables that are resolved at send time. This makes it possible to keep a stable template while passing event-specific data from Supabase.
Prefer explicit variables over raw markup from users
A safe template payload tends to look like a small data contract: recipientName, teamName, inviteUrl, expiresAt, and perhaps inviterName. Avoid passing free-form user HTML into a trusted transactional template.
This has several benefits:
- The design stays consistent across your product.
- Accessibility and plain-language improvements happen in one place.
- Security-sensitive links are constructed from trusted application data.
- Email content can be reviewed without digging through many function files.
- A later localization effort has a better starting point.
Your Edge Function should still validate all values. A variable name is not a security boundary by itself. URLs, display names, and untrusted text need the same care they receive anywhere else in your application.
Observe the entire path from event to inbox
When someone says “email is broken,” the failure could be anywhere: the browser never invoked the function, authorization rejected the request, a database constraint failed, the function did not have a secret, the provider rejected the request, the sender domain was not verified, the recipient address bounced, or the message arrived in a spam folder.
You need enough observability to locate the layer quickly.
Start by assigning a correlation ID to the business operation. Store it with the invitation, payment event, or notification job. Include it in structured function logs. Record the provider response status and message identifier when your application receives one. Then connect provider event data back to the same business record where appropriate.
Metrics worth watching
The exact dashboards will vary, but these signals are useful from the beginning:
- Edge Function invocation count, errors, and latency
- Authorization failures and validation failures
- Number of email handoff attempts
- API acceptance and rejection rates
- Retry count and idempotency replays
- Bounces and complaints
- Delivery-related events where available
- Template-level send volume
- Domain and sender-level trends over time
Do not use open rate as the sole health metric. Privacy features and mailbox behavior make opens an imperfect signal. For transactional email, stronger product signals are often completion outcomes: did the invited user join, did the reset link get used, did the receipt generate a support ticket, or did the security notification prompt the intended action?
A production rollout plan for Supabase email
The fastest path is not necessarily the safest one. Start with a narrow email use case, prove the complete path, then expand.
Phase 1: establish the sender
Authenticate the domain or subdomain you intend to use. Use a clear sender identity, add the required DNS records exactly as shown in Volanea, and verify the sender before routing production messages through it.
Send controlled test messages to mailbox accounts you own across a few major providers. Check the visible From name and address, the subject, the preheader, link behavior, plain-text fallback, and whether the message is recognized as expected.
Phase 2: ship one critical workflow
Choose one high-value transactional flow such as a team invitation or account verification. Build an authenticated Edge Function that uses a server-side secret, persists a business record, and sends with an idempotency key.
Test normal success, invalid recipients, duplicate requests, lost-response retry behavior, unauthorized callers, expired links, and local-versus-production secret configuration. A simple function with excellent failure behavior is more valuable than a broad integration you cannot debug.
Phase 3: add templates and events
Move repeated message structure into templates. Add variables with a documented contract. Capture the provider response and relevant event outcomes against your own business records.
At this stage, write a small internal runbook: where the secrets live, who can change sender DNS, how to rotate a key, what to inspect when a send fails, and how to handle a suspected abuse incident.
Phase 4: scale responsibly
As volume grows, split transactional and campaign workflows, use background or scheduled processing for bulk work, and monitor bounce and complaint signals. If you have high-value flows such as login links or billing notices, add a resend policy that is deliberate rather than automatic.
A resend button should not produce an unlimited stream of messages. Apply cooldowns, rate limits, and user-visible feedback. Reliability includes knowing when not to send.
Supabase and Volanea: a clean division of work
Supabase is the place where your application knows what happened. It knows the current user, row-level permissions, team membership, database state, storage objects, and the business event that should trigger a message.
Volanea is the sending layer that turns that trusted event into email infrastructure work: a message request, sender-domain requirements, suppression handling, delivery processing, and sending-oriented observability.
The combination is especially effective because the integration does not ask your edge runtime to behave like a traditional server. You do not need to keep SMTP connections warm. You do not need a Node-only mail library. You do not need to expose an email credential to the client. You make a small HTTPS request from the function runtime that already sits next to your Supabase data and auth logic.
For developers, that means fewer moving pieces. For users, it means account emails, receipts, invitations, and alerts can arrive from a professional authenticated sender rather than an improvised development-only mail path.
FAQ
Can I use SMTP to send email from Supabase Edge Functions?
Not as the normal hosted Edge Functions path. Supabase blocks outbound connections to ports 25 and 587, which are common SMTP submission ports. Use Volanea’s REST API over HTTPS from an Edge Function instead.
Where should I store my Volanea API key in Supabase?
Store it as a Supabase project secret and read it only inside an Edge Function with Deno.env.get("VOLANEA_API_KEY"). Do not put the key in browser-accessible environment variables, client code, or a public repository.
Why should I use an idempotency key when sending email?
An idempotency key makes retries safer. If a network failure leaves your application unsure whether a send was accepted, retry the exact same logical send with the same key rather than risking a duplicate email.
Does a successful send request guarantee inbox placement?
No. It means the email request was accepted for processing. Inbox placement depends on sender authentication, recipient mailbox behavior, message content, address quality, reputation, and recipient engagement. Monitor delivery-related events and product outcomes rather than treating a single API response as final delivery proof.
Should my frontend call the Volanea API directly?
No. Call your Supabase Edge Function from the frontend, and let the function authorize the action, construct trusted message data, and call Volanea with the server-side API key.