Send email from a Bolt app
Bolt runs your project in the browser and edits it as you describe changes, so adding email is a request rather than a terminal session. The one thing to get right is where the key lives: in the project .env and read from server code, and set again on whatever host you deploy to.
Paste into Bolt
Copy this. The page it names is served as markdown at that exact URL, so the assistant reads the real integration rather than guessing at an API shape.
Add transactional email to this project using emails.sh so signing up sends a verification email.
Read https://emails.sh/docs.md first so you use the real request shape instead of guessing it.
Requirements:
- Install @emails.sh/sdk and use it from server-side code only. Do not import it into a component that renders in the browser and do not put the key in any client bundle.
- Read the key from EMAILSSH_API_KEY. Add it to .env with a placeholder, add .env to .gitignore, and tell me to paste my real key from https://emails.sh/dashboard. If the variable is missing at runtime, throw an error that says so.
- Send from onboarding@emails.sh until I verify my own domain.
- Remind me at the end that I need to set EMAILSSH_API_KEY on my host as well, because .env does not deploy with the project.Bolt adds the SDK to the project.
It writes @emails.sh/sdk into package.json and installs it in the in-browser environment, no terminal needed from you.
It creates .env and the ignore rule.
EMAILSSH_API_KEY sits there with a placeholder until you paste the esh_ key you made in the dashboard.
It writes a server route that sends.
One endpoint that reads the key from the environment, calls POST /v1/emails, and returns the id. The frontend calls that endpoint.
You set the same variable on the host.
Bolt deploys to a host that has its own environment settings. .env is a local file and does not travel, so the deployed build needs EMAILSSH_API_KEY set there too.
What Bolt writes
server/routes/send-verification.ts, the endpoint your form posts to.
import { Emailssh } from '@emails.sh/sdk';
// Reading the variable at module load means a missing key is a startup error
// rather than a request that quietly does nothing.
const apiKey = process.env.EMAILSSH_API_KEY;
if (!apiKey) throw new Error('EMAILSSH_API_KEY is not set');
const emails = new Emailssh(apiKey);
export async function sendVerification(email: string, token: string) {
const link = `https://acme.com/verify?token=${token}`;
const { id } = await emails.send({
from: 'Acme <onboarding@emails.sh>',
to: [email],
subject: 'Confirm your email',
html: `<p>One click and you are in.</p><p><a href="${link}">Confirm your email</a></p>`,
text: `One click and you are in. Confirm your email: ${link}`,
idempotencyKey: `verify:${token}`
});
return id;
}Worth knowing
No terminal means the package comes in through the project files
You do not run `npm install` yourself. Bolt edits package.json and installs for you, so ask for the package by name and let it handle the install step.
A .env file does not deploy
It is ignored by git on purpose. Whatever host the project ends up on needs EMAILSSH_API_KEY set in its own environment settings, with the same name.
Client-side sends leak the key
If the send ends up in a component, the key is in the JavaScript every visitor downloads. Revoke it at https://emails.sh/dashboard and move the call to the server.
Ask it to fetch the docs
Given no reference, Bolt writes the request body of whichever provider it saw most in training. emails.sh takes `to` as an array plus `html` and `text`, and returns { "id": "...", "status": "queued" }.
It can write your lifecycle sequences as a file
An automation here is a YAML document: a trigger, an optional filter, and a list of steps that each send, wait, or branch. GET https://emails.sh/v1/automations/<id>.yaml returns it, PUT the same path replaces it, and a document you push is stored as the exact bytes you sent. So an assistant can write a trial sequence into your repository, you review the diff like any other change, and CI pushes it on merge. Errors from the parser name the wrong thing, say what to write instead, and give a line number, which is what lets an assistant correct itself. Note that this runs over the REST API rather than over MCP: there are no automation tools on the MCP server.
What arrives
One call to POST /v1/emails, and this is the message. The delivery result for it is on GET /v1/emails/:id a second later.
To: hello@example.com
Subject: Confirm your email
One click and you are in. Confirm your email: https://acme.com/verify?token=c72e40Questions
Can Bolt install the SDK without a terminal?
Yes. It edits package.json and installs in its own environment. You describe what you want, it handles the dependency.
Why did sending stop working after I deployed?
Nearly always the environment variable. .env stays in the project, the host needs its own copy of EMAILSSH_API_KEY.
Do I need a domain first?
No. onboarding@emails.sh sends from the first minute. Add your own domain when the from address matters to you.
What happens if I send the same email twice?
Pass idempotencyKey. A repeat call with the same key returns the original id instead of sending again, which covers a double-clicked submit button.