Running · page 7 of 7
Email through Resend, the outbox, and which events send one.
Foreman emails you when the run needs you or has stopped: a halt, a pause, a blocked or failed story, a slot that is parked or down, and a few run-wide warnings. It does not email you for every story that ships. This page is for whoever sets up or receives those emails. Email goes through Resend; without it, every notification is written to an outbox folder instead.
Setting up Resend
The notifier is the notify block of
foreman.config.json. notify.adapter is none (the
default: outbox only) or email-resend. The block takes nothing else but to, from and
subjectPrefix; any other key is a config error.
"notify": {
"adapter": "email-resend",
// Optional. Each falls back as "Addresses and subject prefix" describes.
"from": "foreman@acme.example",
"to": "you@acme.example",
"subjectPrefix": "[acme factory]"
}
Resend needs two keys, and the Set up tab’s Notifications step is the place to save them: it
puts each in .env.foreman at mode 600, out of git, so you never edit an env file by hand.
- Open the dashboard’s Set up tab and the Notifications step. It asks “How should Foreman tell you when a story ships, fails or needs an answer?” with two choices: “Email, through Resend” and “No email: notifications go to the outbox only”.
- In Resend, make an API key (under API Keys) and save it on the
RESEND_API_KEYcard. The step accepts only a key that starts withre_. - Verify a sending domain in Resend (under Domains), then save an address on that domain, such
as
foreman@acme.example, on the Sender address card (RESEND_FROM_EMAIL). Until a domain is verified, Resend sends only to your own Resend address. - Your address goes on the Your address card (
FOREMAN_NOTIFY_EMAIL); leave it empty to use your git email. - Press “Send a test email”. The step tells you what happened: “Sent to you@acme.example. It
should arrive within a minute; check spam too.”, or what to fix: the key was not accepted or
is not active, the sender’s domain is not verified, Resend only sends to your own address
until a domain is verified, too many emails were sent (try again in a minute), Resend could
not be reached, or “Save RESEND_API_KEY first.” when a key is missing. With
features.notificationsoff, or the adapter set to none, the test goes to the outbox only and the step says so.
A test email can be sent only from a dashboard that listens on this computer alone; from
another machine, open the dashboard through an SSH tunnel. foreman doctor checks that the
key names the adapter needs are set, never their values. In the cloud, add RESEND_API_KEY
(and RESEND_FROM_EMAIL when the config has no from) to
cloud.envVars.
When an email cannot be sent, Foreman says so in one line and the run carries on: “Not sent: needs RESEND_API_KEY, RESEND_FROM_EMAIL and a recipient (missing: RESEND_API_KEY).”, “Not sent: Resend answered 403: <Resend’s message>.”, or “Not sent: Resend answered nothing.” when Resend could not be reached or took longer than 15 seconds.
Which events email you
The supervisor sends each email by running foreman notify --kind <kind>, with --slot,
--story, --reason, --parked-for crash|dirty|owner, --stories and --shipped (this
run’s counts) and --log (the session’s log, for its last words) as the kind needs. You can
run the same command by hand; it exits 1 with a “Not sent” line when nothing went out.
| Kind | Subject | When it is sent |
|---|---|---|
halted |
“Factory halted · ACME-12 · merge” | A release failed and froze releases. The body has the step, the reason, the pull request and what to do; for a failure at the live stages it adds the rollbackHint a ship step’s options set, unless Foreman already rolled back. |
stopped |
“Factory stopped · 3 of 5 shipped” | The run stopped, for any reason; a run started with --once sends none when it ends. “Nothing is wrong.” unless the reason says otherwise. |
blocked |
“ACME-12 is blocked · the factory moved on” | A story stopped on a question, an owner task or a depends_on proposal and the run moved on. Not sent when a story only waits on other stories. |
failed |
“ACME-12 did not pass review · parked as a draft” | The story was still rejected after its fix rounds and is parked as a draft pull request with the open findings. |
noresult |
“Factory: a story ended without a result” | A session ended without saying what happened. The story is resumed once; its last words are in the email. |
parked |
“Slot w2 parked · ACME-12” | The slot needs you: the session crashed twice on the story, the story hit the hard build limit, or the folder holds changes that belong to no story. The body names the buttons that apply. |
down |
“Slot w2 is down” | The slot’s environment or worktree would not start. See Slots and environments. |
paused |
“Factory paused” | Foreman paused itself: three misses, environment endings, credits with nothing to switch to, an expired sign-in, or an environment down even after a repair. When the pause retries by itself, the body says when. |
config |
“foreman.config.json is invalid” | The config turned invalid under the run, which carries on with the last good one. Sent once per problem. |
credits |
“Platform credits ran out” or “The API key reached its usage limit” | Sessions switched from the Console API key to your subscription. |
unexpected-stop |
“Foreman was asked to stop, not by you” | Something other than your terminal, a browser on the dashboard or the cloud runner asked the run to stop. Sent at once; the body names who. |
stuck |
“Foreman is not making progress” | The run is going but the progress watchdog says nothing moves. One per episode. |
standby |
“Foreman is on standby: things wait on you” | The run has nothing to build while things wait on you. See the next section. |
owner-task |
“An owner task still holds ACME-12” | An owner task has held a story longer than watchdog.ownerTaskHours. See the next section. |
test |
“Test email” | You pressed “Send a test email” on the Set up tab. |
Every email has the same shape: a label (Halted, Paused, Stuck, Standby and so on), a headline,
one paragraph saying what happened, a fact table (Slot, Story, Stopped at, Pull request, This
run with its story and shipped counts, When), a numbered “What to do”, and, when the session
left one, its own final message under “What the factory said”, cut to 6,000 characters. A
project whose console sets commandCenterUrl or decisionsUrl gets buttons to those pages;
the dashboard console sets neither. The plain-text copy carries the same content.
There is no per-story “shipped” email. A story that ships moves to done in the dashboard, in
foreman status and in its tracker (the shipped transition in Jira, the Case status with
Salesforce), and foreman report counts it. The nearest thing to a ship notice is the
stopped email’s “3 of 5 shipped”, and the standby email when the backlog runs dry.
Troubleshooting for each kind is on When things go
wrong.
The standby email and owner-task reminders
When the run reaches standby (nothing left to build, waiting for new stories) while things wait
on you, it logs one event and sends one standby email. Its text counts what waits, leaving out
the Later and FYI groups, and says what one answer would start:
- “Standby: 3 follow-up proposals and 1 owner task wait on you; finishing ACME-300 starts ACME-301.”
- “Standby: 2 decisions wait on you; one answer starts ACME-12.”
- “Standby: 1 follow-up proposal waits on you; answering the follow-up proposals creates their stories.”
- “…; no answer starts a story by itself.” when nothing waiting holds a story back.
The thing named is the one whose answer frees the most stories. Nothing is sent when nothing
waits on you, and the email is not sent again while its text is the same, so a run that drops
in and out of standby does not repeat it. The body says the run stays up and starts by itself
as soon as something can be built, and points you at Waiting on you or foreman decide list.
Follow-up proposals do not wait for standby any more: foreman learn runs after each shipped
story, so they are usually there before the backlog runs out.
An owner task is a story for you, not for Foreman, that other stories depend on. When one that
is waiting on you now blocks a story, Foreman starts a clock the first time it sees it holding
something back (stories carry no created time). Once the clock passes
watchdog.ownerTaskHours
(24 by default, fractions allowed), it logs one event and sends one owner-task email: “Owner
task ACME-1 “Add the Resend key” has held ACME-2 for 5 days.” It tells you once per task; a task
that stops blocking and later blocks again starts a fresh clock. The check runs about every
50 seconds, never in a dry run and not once a stop has begun. Do the task, then press Mark done
on Waiting on you or run foreman story done <task id>; the story it held starts by itself.
See Owner tasks.
The outbox
With notify.adapter: none, or with the
notifications switch off in either layer,
nothing is sent. Every notification is written instead to .foreman/outbox/, one file per
email named <YYYYMMDD-HHMMSS>-<kind>.json, such as 20261109-140502-paused.json, holding the
kind, the subject, the plain text, the HTML and the time. foreman notify prints “Wrote
<file>” for each. The folder is in the state folder, so it is not committed.
The outbox is the quickest way to see what Foreman would have said without setting up email,
and a record of every notice when email is on but you turn the switch off for a noisy day. It
is not the same as .foreman/jira-outbox/ or .foreman/salesforce-outbox/, which hold
comments and status changes the tracker did not take yet; those are sent again once it answers.
Addresses and subject prefix
The sender is notify.from in the config, else RESEND_FROM_EMAIL. The config wins: the Set up
step says “foreman.config.json sends from <address>, and that wins over this key.” The address
must be on a domain verified at Resend.
The recipient is one address, found in this order: notify.to, then FOREMAN_NOTIFY_EMAIL,
then FACTORY_NOTIFY_EMAIL (the older name, still read so existing env files keep working),
then the user.email of your git config. notify.to
is a setting, so foreman settings set notify.to you@acme.example sets it for this computer
and --team writes it into the config for everyone; the Settings tab shows it only while the
adapter is email-resend. With no address at all, the email is not sent and the line names “a
recipient” among the missing.
Every subject starts with notify.subjectPrefix, which is [<project.name> factory] by
default ([acme factory]), or [Foreman factory] when the project has no name, so a mail
filter can catch them all.
The keys are read from .env.foreman first (only the names Foreman’s key registry lists), then
from the project’s env file, with the process environment winning over both. They are never
printed, never passed to a story session and never put in an email. Each email is sent by a
fresh foreman notify run that reads the config and the keys then, so a change to the block or
the keys counts from the next email.
Was this page useful? Something missing or wrong?Email us and say which section.