# Emails that arrive: confirmation, reset and welcome emails for an AI-built app

> The confirmation email worked for you and never reaches a stranger, because the sender is for development. Find the sender, put your domain behind it, and make the lifecycle emails run on their own.

- Source: https://www.buildbase.app/guides/emails-that-arrive-for-an-ai-built-app
- Section: Guides
- Author: Dharmendra Jagodana
- Published: 2026-10-28

## In short

An AI-built app sends confirmation and reset emails through a development sender until you connect a domain: Supabase delivers only to your project team, two an hour, and BuildBase uses whichever sender was created first. Find the sender, verify a domain, move the default, then make the lifecycle emails workflows.

"Then a real person signs up, never gets the confirmation email, and you
never hear from them again." A dev.to writer put that at the top of a
seven-step guide for beginners on 20 October 2026, and it is the whole
failure in one sentence: the email worked for you, in the preview, and
that is the last time it worked.

You are making three kinds of email arrive, in this order: the ones the
app cannot work without (confirmation, magic link, password reset,
invitation), the ones that make it a product (welcome, trial ending, card
failed), and the ones people opt into (campaigns, digests). When it works,
a stranger signs up from a Gmail address, gets the confirmation in under a
minute from your own domain, the welcome arrives after it, the trial-ending
reminder goes out three days before the trial ends with no code involved,
and every one of those messages is in a log you can open.

What we ran on 27 October 2026: the sender, domain and workflow pieces here
are the ones the published guide on
[transactional email landing in spam](/guides/transactional-email-landing-in-spam)
and the notifications guide are built on; the Supabase and Resend figures
were read from their pages today. We have not sent test mail through a
builder's own pipeline today, so where a builder's behaviour matters it is
named rather than claimed.

> **Before you start:** A domain you control and can add DNS records to. An app with sign-in working,
> either the builder's built-in or BuildBase. A second email address at a
> provider you do not use for work, to test with. For the workflow steps,
> BuildBase sign-in, because the triggers are platform events.

## 1. Find out which sender sent the last email

Before you touch DNS, open the last email your app sent and read the
sender address, because the fix depends entirely on who sent it. There are
three usual answers, and two of them are development senders.

**The builder's or Supabase's default sender.** If your app's sign-in is
Lovable Cloud, Bolt Cloud or your own Supabase project, the confirmation
email comes from Supabase's built-in sender until you change it. Supabase's
docs on custom SMTP say three things about that sender: "Supabase Auth
will refuse to deliver messages to addresses that are not part of the
project's team", "currently this value is set to 2 messages per hour", and
the server "is not meant for production use". That is why your own
confirmation arrived and a stranger's did not: you are on the team, and
they are not, and even on the team the third sign-up in an hour waits.

**A personal Google account somebody connected during setup.** This is
the BuildBase version of the same trap, and it is quieter. System mail
(the magic link, the verification code, the password reset code, the
workspace invitation) does not name a sender; it goes through whichever
single sender in the organization is flagged as the system default, which
is usually the first sender anyone created. Every way of creating a sender
fills that flag only if it is empty, and never moves it. So if someone
connected their own Google account to check that email works, and weeks
later someone else added a verified domain and a Mailgun sender on it,
every sign-in link since has still gone out through Gmail, from the first
person's address, and no DNS record you fix will change that.

**An email API the agent wired directly.** A `fetch` to Resend's or
SendGrid's API with a key from the environment and a hard-coded sender,
usually `onboarding@resend.dev` or a bare address on an unverified domain.
It works in the sense that the request is well formed; Resend's free plan
sends 3,000 emails a month, 100 a day, and the test domain is for testing.

How to prove which one you have: in Supabase, Authentication, SMTP
Settings tells you whether custom SMTP is on; in BuildBase, the email
history row stores the `from` it used and the sender it went through, and
an organization owner can list every sender in the org with its
`systemDefault` flag. If the sender you expected is not the one in the
history, step 3 is the fix.

What you should see: one sentence you can say out loud, "the confirmation
email goes out through X from address Y", before you change anything.

## 2. Put a domain you own behind it

A sender on a domain you own, with the three DNS records that let other
mail servers trust it, is the whole of deliverability for an app this
size. On BuildBase, connect a sender through Google, Mailgun, Amazon SES or
your own SMTP; on Supabase, turn on custom SMTP with a sender from any of
the services its docs name (Resend, SES, Postmark, SendGrid, ZeptoMail,
Brevo), after which mail goes to everyone, at 30 an hour until you raise
the limit on the rate-limits page.

Two decisions before the records:

**A subdomain, not the root.** Send from `mail.example.com` rather than
`example.com`. If a campaign ever damages the sending reputation, it
damages the subdomain, and your personal mail on the root keeps arriving.
It also lets the sign-in mail and the marketing mail live on different
subdomains with different reputations.

**One sender for system mail.** The magic link, the reset code and the
invitation should come from a sender on the verified domain, with a From
name that is your product and a reply-to that a human reads. A reset code
from `no-reply@mail.example.com` with the product name is fine. One from
`firstname.lastname@gmail.com` is the first thing a stranger notices.

The records themselves (SPF, DKIM, DMARC) are three TXT entries your DNS
provider shows you how to add, and the sender's own page prints the values.
What matters more than adding them is checking them, which is what the
next guide does: [transactional email landing in
spam](/guides/transactional-email-landing-in-spam) reads the
`Authentication-Results` header on a copy in the spam folder and tells you
which of the three failed, instead of guessing. On BuildBase, domain
verification runs through Mailgun regardless of which provider delivers a
given sender, so a verified sending domain needs Mailgun connected even if
SES delivers the mail.

### The three records, in plain words

You do not have to understand the records to add them, but you do have to
understand them to read a failure, so here is each in one breath.

SPF is a list, published on your domain, of the servers allowed to send
mail that claims to come from it. If Mailgun sends your reset code and
Mailgun is not on the list, the receiving server has no reason to believe
the message is yours. The sender's page tells you the one entry to add, and
a domain can carry only one SPF record, so a second sender joins the same
record rather than getting its own.

DKIM is a signature. The sending server signs each message with a private
key, and the public half sits in your DNS so the receiver can check that
nothing changed in transit and that the signer was allowed to sign for
your domain. It is the long record, and the one most often pasted wrong,
so do not mark the step done until the sender's page says it verified.

DMARC is the policy that says what to do when the other two fail, and
where to send the reports. Start at `p=none`: nothing is rejected, and the
reports show you which legitimate mail is failing before you tighten it.
The one thing to know is that DMARC also checks that the domain in the
From line is the domain the other two records were checked for. That is
why a message sent through a sender verified on your domain, with a From
address at gmail.com, can pass SPF, pass DKIM and still fail DMARC, and it
is the third branch of the spam guide's decision tree.

Prompt for it, if the agent is doing the wiring:

```text
Connect a sender on mail.<my domain> for all system email and set it as the organization's default sender. Do not send any email from the agent's own code through Resend, SendGrid or another API; every email goes through the platform's sender. Show me the three DNS records I need to add and which one failed if verification does not pass.
```

What you should see: the domain shows verified in the console, and a test
email from the sender arrives at your second address with the From name
and the domain you chose. Then open the message's headers and look for
`spf=pass`, `dkim=pass` and `dmarc=pass`. Three passes is done; anything
else is the spam guide's decision tree.

## 3. Move the default, then test as a stranger

Moving the system default to the sender on your verified domain is one
call, and it has to happen before you delete anything, because the delete
route refuses to remove a sender while it holds the flag:

```bash
curl -X PATCH https://api.console.buildbase.app/api/emails/senders/$SENDER_ID/default \
  -H "Authorization: $BUILDBASE_TOKEN"
```

The sender's owner can do it, and so can an organization owner or admin.
On Supabase, the equivalent is enabling custom SMTP with the new sender's
host, port, username and password in the project's Authentication
settings; once saved, the built-in sender is no longer used.

What you should see: sign up from your second address, at a provider you
do not use for work (a Gmail account if you build on Outlook, or the
reverse), from a private window, and time the confirmation email. Under a
minute, from your domain, in the inbox. Then request a password reset and
a magic link and check both come from the same sender. If the confirmation
arrives and the reset does not, two senders are in play and the default
did not move.

**Correcting potential issues.** If the agent "fixed" this by adding an
email API call inside the sign-up route, paste: "Remove the email code from
the sign-up route. Confirmation, reset and invitation emails are sent by
the platform through the default sender; the app sends nothing itself."
If a sender shows as verified but mail still fails DMARC, the From domain
on the message is not the one that was checked, which is the spam guide's
third case.

## 4. The welcome, the trial ending, the card that failed

These three emails are the difference between an app and a product, and
generated apps never send them because nobody asked. On BuildBase they are
workflows rather than code: a trigger on an event the platform already
knows about, an email step with a template, published without a deploy,
with every run logged per step.

The triggers are the platform's own events, and three matter on launch
day:

- **A sign-up.** The welcome email, with the one thing you want a new
  person to do first. Keep it to one link.
- **`subscription.trial_will_end`.** A heads-up a few days before the
  trial ends, with a link to the plan screen. This is the single most
  effective email a subscription product sends.
- **`payment.failed`.** The card failed; here is the link to update it.
  The platform sends this one itself when the console notification or a
  workflow is on, so the webhook handler in your app is for your own side
  effects (flag the account, post to Slack), not for the email.

In the console's workflow editor those are three triggers and three email
steps, each with a template in HTML, Markdown or plain text and merge tags
for the person's name and the workspace. Conditions and waits go between
steps ("wait three days; if they have not created a project, send the
nudge"), and a follow-up stops once the person has done the thing, which
is the part a cron job the agent wrote never gets right. More than sixty
triggers exist; these three are the ones that pay for themselves in the
first month.

Here is the trial reminder as it looks in the editor, so that "no code"
means something concrete. The trigger is `subscription.trial_will_end`.
Step one is a condition on the workspace's subscription, because a
customer who already added a card does not need the nudge. Step two is an
email to the workspace owner, from your default sender, with a merge tag
for the date the trial ends and one button to the plan screen. Step three
is a wait of two days, then a second email only if the condition still
holds. Every run lists each step with its time and outcome, so "did the
reminder go out to Acme" is a search in the console, not a dig through
logs.

Prompt for the parts that touch the app:

```text
Do not write welcome, trial-ending or failed-payment emails in code. I will create them as workflows in the console. In the app, make sure the sign-up, trial and subscription events reach the platform (they do by default through BuildBase sign-in and billing); do not add a cron job, a queue or an email API for these.
```

What you should see: sign up as a stranger and the welcome arrives after
the confirmation, from the same domain. Start a trial on a test card, and
the reminder is scheduled; the workflow's run log shows the wait step
pending. Let the test card fail on renewal and the failed-payment email
goes out, and the run log shows it did. If any of those shows nothing, the
trigger is not wired, and the log says which one.

## 5. The emails people can turn off, and the one they always can

A person should be able to choose which emails they get, and every email
that is not required to operate the account should carry a one-click
unsubscribe. The sign-in emails (confirmation, reset, magic link) are the
account working and always send; everything else is a preference.

BuildBase ships the preference screen: a settings page where a user picks
which notifications they receive, with one-click unsubscribe and a
`List-Unsubscribe` header on campaign mail so mail clients show their own
unsubscribe button. [Which notifications users get to turn
off](/guides/which-notifications-users-can-turn-off) is the guide to
deciding what goes on that screen, and the rule of thumb is short: if a
person would be angry to receive it, it is optional; if they would be
angry not to, it is required.

What you should see: the settings page shows the preference list, turning
one off stops that email on the next run of its workflow, and a campaign
sent to a list shows an unsubscribe link that works in one click. On
Supabase-only apps there is no preference screen until you build one, and
the agent's version is usually a boolean column checked by nothing.

## 6. What the email says, and what it should not

The last check is the content, and it is the one the spam filters do not
catch. A non-developer who built a mental-health app in five weeks and
spent three months getting it ready for real users listed, among the
things he had to fix, that "booking confirmation emails included the
reason for the session". The email worked. It leaked.

Three rules that cover most of it. A transactional email says what
happened and what to do next, and nothing the person did not type into the
email field themselves; a sensitive field belongs behind the sign-in, with
a link. The sign-in emails carry a code or a link that expires, and never
a password. And a message to a user names the user, not "Dear user", which
is both the right thing and a signal the filters read.

What you should see: open each of the emails from steps 3 and 4 and ask
whether you would be comfortable if it were forwarded. If not, move the
detail behind a link.

## Where the builder's own sender wins

For a single-user app or a tool for your own team, the sender the builder
gives you is fine, and the limits that break it for strangers (team-only
delivery, two an hour) are limits you will never hit. Replit and Bolt
connect Resend and SendGrid as connectors, v0 connects Resend in one
click, and Resend's free plan at 3,000 emails a month covers a small app
with no domain of its own for a while. If your app sends one kind of
email and you already have a Resend account, connect it and skip the rest.

Choose a platform sender with workflows when the emails are the product's
behaviour rather than a notification: a trial that ends on its own, a card
that fails and a reminder that follows it, a preference screen, campaigns
to a list, and a log you can open when a customer says "I never got it".
Lovable's Cloud page lists no email at all and points to a separate guide
on sending from your own domain; Bolt's and Replit's connectors send, and
leave the rest to code. [The launch
checklist](/guides/vibe-coded-app-launch-checklist) puts email where it
sits among the other nineteen things.

> **Key takeaway:** The confirmation email that only you receive is not a bug in the email. It is
> a development sender doing exactly what its docs say. Find the sender, put
> your domain behind it, move the default, and make the lifecycle emails
> workflows you can watch run.

## What breaks, and how you can tell

**You get the confirmation, strangers do not.** Step 1: the development
sender delivers only to the project's team, and only two messages an hour
even then.

**The reset code arrives from a personal Gmail address.** Step 3. Somebody
connected their own account first, and the system default never moved.

**The domain says verified and mail still goes to spam.** Open the copy in
the spam folder and read `Authentication-Results`, as the spam guide walks
through: SPF or DKIM failing points at a record, and DMARC failing while
both pass means the From domain is not the one that was checked.

**The welcome arrives, the trial reminder never does.** Step 4. Either the
wait step is still pending or the trigger is not wired, and the run log
says which.

**A user turned off notifications and still gets them.** Step 5. The
preference is a column nobody's code reads; the platform's preference
screen is the one the workflows honour.

**The sender cannot be deleted.** It holds the system default flag. Move the
default first.

## Install

The sender, the workflows and the preference screen live in the console;
the app needs only the SDK and its sign-in routes:

```bash
npm i @buildbase/sdk
npx buildbase init --org-id <your 24-character org id>
```
