Mail lets deployed apps send transactional messages through the sendmail binary, SDK, or GraphQL and inspect sent or received messages in one API. Use it for sign-in flows, invites, receipts, notifications, and workflow updates.
Send email with the sendmail binary
StackMachine uses Anybuild to build apps from source. Add this app environment variable before building or redeploying:
| Name | Value |
|---|---|
ANYBUILD_EXTRA_DEPENDENCIES |
["sendmail"] |
The value is a JSON array stored as a string. Anybuild includes the sendmail binary in the app runtime and enables email in the generated app configuration (enable_email: true). Rebuild or redeploy after setting it so the binary is included in the app version.
StackMachine automatically provisions the app’s sender and Sendmail API credentials. Applications can invoke the Sendmail-compatible binary directly; StackMachine queues the messages, manages delivery, and records them in the email API.
Include sendmail in the build
Set the variable in the dashboard’s app environment settings, or pass it through the SDK as shown below. Use the client from Client setup, your workspace name as owner, and the upload URL from Deploy from ZIP.
const deployment = await client.deployments.create({
appName: "mail-app",
owner: "your-workspace",
allowExistingApp: true,
uploadUrl, // Source ZIP from the Deploy from ZIP guide
envVars: [{
name: "ANYBUILD_EXTRA_DEPENDENCIES",
value: JSON.stringify(["sendmail"]),
sensitive: false,
}],
});
const appVersion = await deployment.wait({
onProgress: (event) => console.log(event.stream, event.message),
});
console.log(appVersion.app.url);import json
deployment = client.deployments.create(
app_name="mail-app",
owner="your-workspace",
allow_existing_app=True,
upload_url=upload_url, # Source ZIP from the Deploy from ZIP guide
env_vars=[{
"name": "ANYBUILD_EXTRA_DEPENDENCIES",
"value": json.dumps(["sendmail"]),
"sensitive": False,
}],
)
app_version = deployment.wait(
on_progress=lambda event: print(event.stream, event.message),
)
print(app_version.app.url)Send from your application
Run this in your deployed app’s server code. sendmail -t reads recipients from the message headers and the message itself from standard input. StackMachine supplies the app’s default sender. The binary uses the app’s provisioned credentials, so this application code does not need an SDK client or workspace API token.
import { spawn } from "node:child_process";
const message = [
"To: user@example.com",
"Subject: Welcome to StackMachine",
"Content-Type: text/plain; charset=UTF-8",
"",
"Thanks for signing up.",
"",
].join("\n");
const child = spawn("sendmail", ["-t"], {
stdio: ["pipe", "inherit", "inherit"],
});
const submitted = new Promise((resolve, reject) => {
child.once("error", reject);
child.stdin.once("error", reject);
child.once("close", (code) => {
if (code === 0) resolve();
else reject(new Error(`sendmail exited with code ${code}`));
});
});
child.stdin.end(message);
await submitted;
console.log("Email submitted to StackMachine");import subprocess
from email.message import EmailMessage
message = EmailMessage()
message["To"] = "user@example.com"
message["Subject"] = "Welcome to StackMachine"
message.set_content("Thanks for signing up.")
subprocess.run(["sendmail", "-t"], input=message.as_bytes(), check=True)
print("Email submitted to StackMachine")A successful exit confirms submission. Delivery continues asynchronously; inspect the message status in the API or dashboard.
Find the message in the API
Messages sent through the binary appear in the same sent-email history as messages sent through the SDK or GraphQL. Use your workspace API token with the SDK client and replace da_XYZ with the sending app’s ID. You can also inspect the messages under Emails in the dashboard.
const sent = await client.emails.sent.list({ app: "da_XYZ", limit: 10 });
for (const message of sent.data) {
console.log(message.id, message.subject, message.status);
}sent = client.emails.sent.list(app="da_XYZ", limit=10)
for message in sent.data:
print(message.id, message.subject, message.status)Send from the dashboard or API
Send directly from Emails → Send email in the dashboard. The platform uses the app’s default sender and prepares it automatically on the first send, even if email was not enabled in the app configuration. You can choose another available app sender in the composer. Domain verification happens before delivery, so no verified sender is required to submit the message.
The composer requires at least one To recipient, a nonblank subject, and message content. Cc and Bcc are optional, and the combined recipient count cannot exceed 50. Plan allowances and storage limits still apply; submitting an email queues it rather than confirming delivery.
For API clients, sendAppEmail also accepts an RFC822 rawMessage through a GraphQL multipart upload. Its To and Subject headers can supply those fields, so to and subject are optional in the schema. The resulting message must still contain recipients, a subject, and content. Raw uploads cannot be combined with textBody or htmlBody.
Features
Transactional sends
Send email from an app with recipients, subject, plain text, HTML, cc, bcc, reply-to, and optional sender fields.
Sent and received history
List sent or received messages by app or owner, including messages submitted through the sendmail binary. This gives support and operations tooling a single place to inspect message status.
Delivery status
Email messages include a status such as queued, sent, delivered, failed, or received so product flows can surface what happened.
Common workflow
- Send transactional email from the app backend.
- List sent messages when confirming a product flow.
- List received messages by app or owner for inbound workflows.
- Use message status and timestamps during support investigations.
SDK examples
Send and inspect messages
Send a welcome email, list recent sent messages for the app, and read received messages for an owner.
const message = await client.emails.send({
app: "da_XYZ",
to: ["user@example.com"],
subject: "Welcome to StackMachine",
textBody: "Thanks for signing up.",
htmlBody: "<p>Thanks for signing up.</p>",
replyTo: "support@example.com",
});
console.log(message.id, message.status);
const sent = await client.emails.sent
.list({ app: "da_XYZ", limit: 10 })
.autoPagingToArray({ limit: 25 });
console.log(sent.map((item) => [item.subject, item.status]));
const received = await client.emails.received.list({
owner: "owner_id_example",
limit: 10,
});
console.log(received.data);from itertools import islice
message = client.emails.send(
app="da_XYZ",
to=["user@example.com"],
subject="Welcome to StackMachine",
text_body="Thanks for signing up.",
html_body="<p>Thanks for signing up.</p>",
reply_to="support@example.com",
)
print(message.id, message.status)
sent = list(islice(client.emails.sent.list(app="da_XYZ", limit=10), 25))
print([(item.subject, item.status) for item in sent])
received = client.emails.received.list(owner="owner_id_example", limit=10)
print(received.data)Related docs
Source: stackmachine/sdks JavaScript and Python examples.