Zed transactional email is one of those small integration jobs that determines whether an AI-built app feels real. Your sign-up flow may work locally, but until a new user receives a welcome email or a password-reset link, the product is still missing a critical piece of its operating loop.
The practical goal is not to make an agent generate a giant email system. It is to give Zed a narrow, well-specified task: add one server-side email adapter, keep the secret off the client, use a verified sending domain, and call it from a real product event. Once that foundation works, welcome emails, password resets, receipts, invitations, and notifications become repeatable patterns rather than separate experiments.
What Zed transactional email means in practice
Zed is an AI-enabled code editor with an Agent Panel that can read, search, edit, and run code in a project, subject to the tools and permissions you configure. That makes it useful for wiring an email provider into an app: the agent can inspect your framework, find the user-creation path, add environment-variable validation, create a reusable sending function, and run the project checks you already use.
For an app builder, Zed transactional email does not mean email is somehow sent from the editor. It means you use the agent to make a focused change to the application codebase. The application still sends email from its trusted server environment, serverless function, worker, or background job.
That boundary matters. A transactional email provider secret is a sending credential. It must not reach the browser bundle, a mobile app, a public Git repository, an example response, or a client-side NEXT_PUBLIC_ environment variable. An agent can accelerate implementation, but it cannot make a leaked production key safe.
A reliable first use case is deliberately boring:
- A person creates an account.
- Your server persists the account successfully.
- Your server invokes one
sendWelcomeEmail()function. - The function submits the message to Volanea over HTTPS.
- Your app records whether the provider accepted the request and can safely retry without producing multiple welcome messages.
That sequence turns “add email” from an open-ended vibe-coding request into an observable product workflow.
Why email is a good agent-coding task—and a risky one
Email integration is especially suitable for an AI coding agent because the repetitive parts are predictable. Most apps need the same structural decisions: a private environment variable, a server-only module, a message payload, error handling, a call site after a business event, and an integration test or manual test plan.
It is also risky precisely because it is easy to make something that appears to work. A hard-coded address may receive a message in local development while production sends fail because the sender domain was never authenticated. A password-reset endpoint may return success while silently leaking whether an address exists. A retry after a timeout may create two nearly identical messages. A client-side fetch may expose the API key to anyone opening browser developer tools.
The best way to use Zed is therefore not “write whatever is necessary to email users.” It is “make the smallest secure server-side implementation, explain each file changed, and stop if the existing architecture requires a choice.” That gives the agent enough direction to act without inviting it to invent a second authentication system, redesign your database, or expose credentials to get a demo working.
The first integration should have a narrow contract
Your email helper needs a contract that is small enough to test mentally:
- It accepts an intended recipient, subject, plain-text version, HTML version, and a stable idempotency key.
- It uses a sender address at a domain you have verified in Volanea.
- It uses a secret key only on the server.
- It throws a useful error when Volanea rejects the request.
- It does not decide whether a user should receive the message; business logic decides that.
- It does not render untrusted user input directly into HTML without escaping or a proven template strategy.
This separation gives an agent less room to confuse delivery infrastructure with product policy. Your signup service knows a user was created. Your email adapter knows how to submit a message. Your template function knows how to render a specific message. Each layer has one job.
The setup to finish before asking Zed to write code
An agent can create code in seconds, but it cannot complete every account-level decision for you. Before prompting Zed, prepare the pieces that code should reference without ever embedding them.
First, create a Volanea project and a secret API key. Volanea’s sending API uses the https://api.volanea.com base URL and its single-message endpoint is POST /v1/send. Test and live keys are separate, so use a test key while you are proving the integration and a live key only after your sender and production workflow are ready.
Second, authenticate the domain that will appear in the From address. The exact DNS records are provided for your domain in the provider setup flow; copy those published values exactly rather than having an agent guess record names or values. Domain authentication is not optional polish. Recipient providers use authentication signals to assess whether mail from your domain is legitimate, and a beautiful email cannot compensate for a sender identity that is misconfigured.
Third, decide where your deployment platform stores server secrets. For local work, a gitignored .env.local file is common in JavaScript frameworks. In production, use the hosting platform’s encrypted environment-variable or secrets facility. Do not paste a live sk_... value into a Zed chat thread, an issue, a screenshot, or source code.
A simple local file might contain this:
# .env.local — do not commit this file
VOLANEA_API_KEY=sk_test_replace_with_your_test_key
EMAIL_FROM="Acme <hello@mail.example.com>"
APP_URL=http://localhost:3000
The EMAIL_FROM value is configuration, rather than an inline string, because the sender may differ by environment. A staging application should not unexpectedly present itself as your final public product, and a local app should not try to send from an unverified placeholder domain.
If you are choosing a provider plan while building a prototype, compare the included sending allowance, overages, environment separation, and the operational features you will need as your app grows. The details belong in transactional email pricing, not in application code.
A prompt you can paste into Zed
A strong prompt tells the agent what outcome you want, the constraints it must preserve, and the verification steps it should run. It does not pretend the agent already knows your framework or let it choose a client-side shortcut.
Open the project in Zed, start an Agent Panel thread, and paste a prompt like this. Replace the path names only if you already know your project’s structure; otherwise tell the agent to discover the right location first.
Inspect this codebase and add a server-side Volanea transactional email
integration for a welcome email sent after a user account is successfully
created.
Requirements:
- Detect the framework and use the existing server-side architecture.
- Never expose VOLANEA_API_KEY to browser/client code.
- Read VOLANEA_API_KEY and EMAIL_FROM from environment variables and fail
clearly on the server if either is missing.
- Create a reusable email module named lib/volanea.ts, or the idiomatic
equivalent for this project.
- Send through POST https://api.volanea.com/v1/send using JSON, an
Authorization header, and an Idempotency-Key header.
- Use the verified sender address from EMAIL_FROM.
- Add sendWelcomeEmail({ userId, email, name }) with both text and HTML
content. Escape user-provided values before placing them in HTML.
- Generate a stable idempotency key from the user ID and the welcome-email
event so retries do not create a second welcome email.
- Call it only after the database/account creation succeeds. Do not let a
delivery failure roll back account creation; log enough context to debug it.
- Do not modify authentication, database schema, or package dependencies
unless necessary. If a choice is ambiguous, explain it instead of guessing.
- Add or update tests if this repository has an email/service test pattern.
- Run the relevant typecheck, lint, and tests. Then summarize changed files,
commands run, and anything I must configure manually in Volanea.
This prompt does something important: it asks the agent to inspect before it edits. In an agent-coded project, that single sentence often separates a clean integration from a generated module that assumes Express inside a Next.js route, imports Node-only code into an edge runtime, or puts a secret in a component.
It also makes the application behavior explicit. The account exists even if an email request fails. That is usually the correct first decision for a welcome email because the welcome message is valuable but should not prevent someone from using the account. A passwordless login link or mandatory verification code may deserve a different failure policy, but that should be a deliberate product decision.
The resulting TypeScript: a small Volanea email adapter
Below is a framework-neutral TypeScript implementation Zed can create for a Node-compatible server environment. The helper uses the Volanea single-message endpoint, sends JSON, and attaches an Idempotency-Key header. The message shape includes a sender, recipient, subject, plain-text alternative, and HTML body.
// lib/volanea.ts
import { createHash } from "node:crypto";
type SendEmailInput = {
to: string;
subject: string;
text: string;
html: string;
idempotencyKey: string;
};
type WelcomeEmailInput = {
userId: string;
email: string;
name?: string | null;
};
const apiKey = process.env.VOLANEA_API_KEY;
const from = process.env.EMAIL_FROM;
const appUrl = process.env.APP_URL;
function requireEmailConfig() {
if (!apiKey) {
throw new Error("VOLANEA_API_KEY is not configured");
}
if (!from) {
throw new Error("EMAIL_FROM is not configured");
}
}
function escapeHtml(value: string): string {
return value
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll('"', """)
.replaceAll("'", "'");
}
function stableKey(parts: string[]): string {
return createHash("sha256").update(parts.join(":"), "utf8").digest("hex");
}
export async function sendEmail(input: SendEmailInput) {
requireEmailConfig();
const response = await fetch("https://api.volanea.com/v1/send", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
"Idempotency-Key": input.idempotencyKey,
},
body: JSON.stringify({
from,
to: input.to,
subject: input.subject,
text: input.text,
html: input.html,
}),
});
const responseBody = await response.text();
if (!response.ok) {
throw new Error(
`Volanea send failed with ${response.status}: ${responseBody || "no response body"}`,
);
}
try {
return JSON.parse(responseBody) as unknown;
} catch {
return responseBody;
}
}
export async function sendWelcomeEmail({
userId,
email,
name,
}: WelcomeEmailInput) {
const safeName = escapeHtml(name?.trim() || "there");
const destination = appUrl ? `${appUrl.replace(/\/$/, "")}/app` : undefined;
const cta = destination
? `<p><a href="${escapeHtml(destination)}">Open your app</a></p>`
: "";
return sendEmail({
to: email,
subject: "Welcome to Acme",
text: [
`Hi ${name?.trim() || "there"},`,
"",
"Welcome to Acme. Your account is ready.",
...(destination ? ["", `Open your app: ${destination}`] : []),
].join("\n"),
html: [
`<p>Hi ${safeName},</p>`,
"<p>Welcome to Acme. Your account is ready.</p>",
cta,
].join(""),
idempotencyKey: stableKey(["welcome-email", userId]),
});
}
There are a few intentional choices in this code. It uses native fetch, so a basic integration does not require a provider SDK or a mail transport dependency. It accepts both text and HTML because the plain-text content is useful for recipients and clients that do not render HTML. It hashes the logical event name and user ID into a stable key, meaning the same welcome-email operation produces the same idempotency key when retried.
The helper also reads the response as text before attempting JSON parsing. That gives you an error message even when an upstream proxy or unexpected failure returns a non-JSON response. During the first integration, being able to see a status code and provider response is far more useful than a generic “email failed” exception.
Wire the helper in after account creation
The call site will vary by framework and authentication stack. The rule does not: create the account first, then request the welcome email. Here is a simplified server-side signup service:
// lib/register-user.ts
import { sendWelcomeEmail } from "./volanea";
import { db } from "./db";
export async function registerUser(input: {
email: string;
name?: string;
passwordHash: string;
}) {
const user = await db.user.create({
data: {
email: input.email.toLowerCase().trim(),
name: input.name?.trim() || null,
passwordHash: input.passwordHash,
},
});
try {
await sendWelcomeEmail({
userId: user.id,
email: user.email,
name: user.name,
});
} catch (error) {
console.error("Welcome email was not accepted", {
userId: user.id,
error: error instanceof Error ? error.message : String(error),
});
}
return user;
}
For a small app, this is an appropriate first version. For a higher-volume or business-critical workflow, move the send into an outbox or background job after the database transaction commits. That reduces the chance that a web request times out while waiting for an external service and gives you a durable place to retry failed work. The key principle stays the same: use a stable identifier for the event, not a fresh random value for every retry.
Why idempotency matters even for a welcome email
It is tempting to treat a welcome email as harmless. But duplicate mail is still a product bug. It makes a new app feel unreliable, can trigger support tickets, and trains users to mistrust more sensitive email later.
Timeouts create the difficult case. Your server sends a request, but the network drops before your application receives the response. You do not know whether Volanea received the request. Retrying with a new idempotency key risks creating a duplicate message; retrying with the same key lets the provider recognize the operation as the same logical send.
Use an idempotency key that represents the event, not the delivery attempt:
- Good:
welcome-email:<user-id>. - Good:
password-reset:<reset-token-id>. - Good:
invoice-receipt:<invoice-id>. - Risky: a new random UUID each time the request is retried.
- Risky: the recipient address alone, because the same person may legitimately receive different messages.
Volanea supports Idempotency-Key on the send endpoint, which is why it belongs in the initial integration rather than being deferred as enterprise-only reliability work. The provider can also accept up to 50 recipients for a single message send, but product notifications should generally be modeled around the intended event and recipient rather than treating the API as a bulk marketing shortcut.
Password resets need a stricter pattern
Once the welcome message works, the usual next request is “have Zed add password reset email too.” The mechanics are similar, but the security posture is different.
A password-reset implementation should generate a short-lived, single-use token on the server, store only a safe representation of that token where appropriate, and put the tokenized reset URL into the message. Do not put a password, authentication secret, provider key, or database credential in the email. Do not make the token predictable from a user ID. Do not log the raw reset URL in production logs.
The public endpoint should also be careful about account enumeration. A reset form normally responds with a neutral confirmation such as “If an account exists for this address, we sent reset instructions,” whether or not the address is registered. That prevents the endpoint from becoming an easy directory of your users.
The agent prompt should change accordingly:
Add a password-reset email using the existing authentication and token
patterns in this repository. Do not reveal whether an email address exists.
Create a short-lived, single-use reset token using the existing secure token
storage approach. Send the message only from server-side code through the
existing Volanea adapter. Use an idempotency key derived from the reset-token
record, never from the email address alone. Add expiry and replay tests.
This is a useful example of where agent coding helps but cannot replace review. Zed can find existing auth conventions and apply them consistently. You still need to verify the security properties of the workflow: what happens after a token is used, what happens after expiry, whether sessions are invalidated if your policy requires it, and whether the route has appropriate rate limiting.
Testing the integration without fooling yourself
Email tests should prove more than “a function was called.” Split them into three levels.
1. Unit-test your message construction
Test the stable things your application owns: subject text, plain-text fallback, escaped display name, and idempotency key construction. You can mock fetch here because you are not trying to prove Volanea’s network behavior; you are proving your module builds a safe request.
Useful assertions include:
- A name containing
<script>is escaped in the HTML body. - The text version does not include HTML tags.
- The sender is read from configuration rather than hard-coded.
- Missing configuration produces a server-side error.
- Retrying for the same user produces the same welcome-email key.
2. Run a test-key integration check
Use a Volanea test key and a controlled inbox to submit a real message. Confirm the message is accepted, the sender is the verified domain you expect, and the text and HTML versions render well. Do this from the same kind of runtime that will run in production when possible: a deployed preview or staging environment catches configuration errors that local development hides.
Do not use your own personal mailbox as the only test. Test at least a mainstream web inbox and a mobile client if email is central to the product. Look for sender display, link destination, mobile wrapping, and whether the plain-text alternative makes sense.
3. Test the business-event boundary
Create a user and verify the application calls the email action after persistence succeeds. Then simulate a provider failure and verify the correct outcome: the account remains created, the error is recorded, and your user-facing response does not claim delivery was guaranteed.
The exact test implementation depends on your stack, but the desired behavior is framework-independent. This is where many fast integrations go wrong: they make an external email call inside a database transaction or before saving the account. Either ordering can lead to inconsistent results when one system succeeds and the other fails.
Using Zed without pretending there is a native Volanea plugin
There is no need to claim a special Zed-to-Volanea plugin for this workflow. Zed’s Agent Panel can work from your project files, terminal, diagnostics, and configured tools. That is enough for an ordinary REST integration when you give it the endpoint, constraints, and review criteria.
Zed also supports Model Context Protocol (MCP) context servers. MCP can expose tools and prompts to an agent, and Zed supports MCP Tools and Prompts. In a future or custom setup, an MCP server could expose narrow, permissioned operations such as retrieving current email API reference material, listing verified sending domains, or checking a test message status. That is different from giving an agent unrestricted authority over a production email account.
If you experiment with MCP, use the least-privilege version of the idea:
- Prefer documentation or read-only diagnostic tools before write-capable tools.
- Keep live sending credentials outside an MCP configuration committed to the repository.
- Require confirmation for operations that send mail, modify domains, create keys, or change billing.
- Start with a test project and test keys.
- Review every tool call as if you had typed it yourself.
For most teams, the direct REST adapter above is the right first move. It is small, portable, transparent in code review, and compatible with many modern frameworks. If you later add an MCP server, it should improve documentation access or safe operations—not hide the fact that production email remains a real external system with credentials, DNS, deliverability, and consequences.
Common agent-generated mistakes to catch in review
When Zed finishes the task, do not accept the diff just because the code compiles. Review it with an email-specific checklist.
- Secret exposed to the client: Look for public-prefixed variables, client components, browser-side
fetch, or a route returning the key accidentally. - Unverified sender: Make sure
EMAIL_FROMuses a sender at the domain you actually authenticated, not a copied example domain. - Hard-coded environment values: API keys, production URLs, and real recipient addresses should not be in source files.
- No plain-text body: Include a readable
textversion alongside HTML. - Unescaped interpolation: Names, organization labels, and other user-controlled values need escaping before inclusion in raw HTML.
- Fresh retry key: A newly generated UUID on every retry defeats idempotency.
- Email before persistence: Sending before the account or token exists risks informing someone about a record that later fails to save.
- Overbroad error handling: A catch block should preserve useful context for operators without logging secrets, raw reset tokens, or complete sensitive message contents.
- Fake delivery confirmation: API acceptance is not the same as a user reading mail. Keep your product language accurate.
- Unnecessary dependency churn: A one-endpoint REST call should not require a dozen packages unless the existing project architecture clearly benefits from them.
A clean review is one of the best uses of a second Zed thread. Ask a separate agent to inspect the completed diff specifically for client-secret exposure, event ordering, HTML injection, and retry behavior. Separate review context often catches assumptions the implementation thread carried forward.
From one welcome email to a durable email layer
The first working message should not become a copy-pasted blob inside every API route. Turn the basic adapter into a small internal email boundary early.
A practical project structure could look like this:
lib/
volanea.ts # provider transport and request handling
email-templates.ts # welcome, reset, invitation content builders
register-user.ts # business event and persistence
reset-password.ts # token lifecycle and reset event
As the app grows, keep the distinction between transactional and campaign-style work clear. Transactional email is triggered by an individual’s action or account state: a verification request, welcome note, invoice receipt, security alert, invitation, or password reset. It should be timely, expected, and relevant to that user.
Campaign email is planned outreach to a broader audience. It raises separate concerns about consent, unsubscribes, segmentation, frequency, content approvals, and audience policy. Do not repurpose a password-reset transport path into a marketing sender simply because both use email addresses.
Volanea’s API can support reusable templates and contact-oriented workflows when you need them. But do not start there because an agent made it easy to generate abstractions. Start with messages whose code, content, and product event you can understand end to end. The email API reference and setup guides are the right place to verify exact request fields and expand the integration deliberately.
A practical launch checklist
Before enabling real traffic, walk through this list in a deployed environment:
- Your
Fromdomain has completed the provider’s required authentication setup. - Production uses a live secret key stored only in server-side deployment configuration.
- Local and staging environments use test credentials or isolated configuration.
- The application never sends the key or raw provider response to the browser.
- A real new account triggers exactly one welcome email.
- A deliberately repeated event uses the same idempotency key and does not create an unintended duplicate.
- HTML and text versions are readable and link to the correct production URL.
- Provider errors are observable in logs or monitoring without exposing secrets.
- Password-reset flows use secure, expiring, single-use tokens and neutral public responses.
- Someone has reviewed the final diff rather than accepting an agent change wholesale.
This checklist is intentionally more operational than aesthetic. A polished template is useful, but dependable email starts with sender configuration, secrets discipline, event ordering, and failure behavior. Those basics are what let you keep moving quickly as the app gains users.
Conclusion: let Zed build the integration, not the assumptions
Zed can make transactional email feel like a minutes-long task because it can inspect the project, create a narrow server-side module, connect the real signup event, and run checks. That speed is valuable. The lasting result depends on the constraints you give it.
Use a specific prompt. Keep the Volanea key on the server. Verify the sender domain before production. Treat API acceptance and inbox delivery as different states. Use a stable idempotency key. Review the generated change with the same care you would give a hand-written authentication or billing integration.
Do that, and your first Zed transactional email feature is not just a demo that sends a message. It is the beginning of an email layer you can safely reuse for the moments that make an app feel responsive and trustworthy.
FAQ
Can Zed send email directly from the editor?
No. Zed can help an agent write and validate application code, but the email should be sent by your app’s server-side runtime or background worker using a securely stored Volanea credential.
Do I need a native Volanea MCP server to use Zed?
No. A direct REST integration is enough. Zed supports MCP tools and prompts, but you should only add an MCP server when it provides a clear, permissioned benefit such as documentation access or controlled diagnostics.
Should a welcome-email failure block signup?
Usually no. Persist the account first, attempt the welcome email afterward, and record failures for retry or investigation. More critical flows, such as a required verification code, may need different product behavior.
Why send both HTML and plain-text email bodies?
HTML provides richer presentation, while plain text remains readable in clients or situations where HTML is disabled or unsuitable. Providing both is a practical baseline for transactional messages.
What is the fastest safe first test?
Use a Volanea test key, a verified sender domain, and a controlled recipient inbox from a staging or local server-side environment. Confirm one real signup produces one message, then test a repeated request to validate idempotency.