177 lines
7.4 KiB
Markdown
177 lines
7.4 KiB
Markdown
# Mail setup — Google Workspace SMTP, sending as care@nearledaily.com
|
|
|
|
What this is for: the first-password invitation. Every back-office account on
|
|
Fiesta is created with an empty password, and the link in this email is the only
|
|
way to set one — the sign-in screen no longer offers a form, because a public one
|
|
meant that knowing a merchant's email address was enough to claim their account.
|
|
|
|
So this is not newsletter plumbing. **If the mail lands in spam, a business that
|
|
was just onboarded cannot sign in**, and the first anyone hears of it is a phone
|
|
call. Step 3 is the one that decides that, and it is the one people skip.
|
|
|
|
---
|
|
|
|
## The decisions
|
|
|
|
| | | why |
|
|
|---|---|---|
|
|
| Relay | Google Workspace SMTP | no server to run, no IP to warm, no port 25 exception to beg for. At a few dozen invitations a month that is the whole argument |
|
|
| Sender | `care@nearledaily.com` | the link points at `app.nearledaily.com`; a password mail whose sender and destination are different domains is the shape of a phishing mail |
|
|
| `care@` not `no-reply@` | | somebody replying "I never got this" is the most useful reply this system can receive, and it should reach a person |
|
|
| Auth | an App Password, never the login password | it can be revoked on its own if it leaks |
|
|
|
|
This replaces an earlier plan to self-host Postal. Postal is the better answer at
|
|
volume; it is the wrong answer for tens of emails a month, because the work is
|
|
not the software — it is IP reputation, rDNS and blocklists.
|
|
|
|
---
|
|
|
|
## Step 1 — Google Workspace on nearledaily.com
|
|
|
|
1. Sign up at workspace.google.com with `nearledaily.com`. Business Starter is
|
|
enough.
|
|
2. Verify the domain with the TXT record Google gives you.
|
|
3. Create `care@nearledaily.com`. It is a real mailbox and **somebody has to read
|
|
it** — merchant replies and bounce notices both land there, and a bounce is how
|
|
you learn an invitation never arrived.
|
|
|
|
## Step 2 — DNS on nearledaily.com
|
|
|
|
| Record | Name | Value |
|
|
|---|---|---|
|
|
| MX | `nearledaily.com` | `smtp.google.com` (priority 1) |
|
|
| TXT (SPF) | `nearledaily.com` | `v=spf1 include:_spf.google.com ~all` |
|
|
| TXT (DKIM) | `google._domainkey` | the key from Step 3 |
|
|
| TXT (DMARC) | `_dmarc` | `v=DMARC1; p=none; rua=mailto:care@nearledaily.com` |
|
|
|
|
- **One SPF record only.** If the domain already has one, merge
|
|
`include:_spf.google.com` into it. Two SPF records is a permerror and fails
|
|
every check.
|
|
- **Remove old MX records** if the domain receives mail somewhere else today, or
|
|
that mail keeps going to the old place.
|
|
- **Keep DMARC at `p=none`** for a couple of weeks, read the reports, then move to
|
|
`p=quarantine`. Going straight to `p=reject` is how you find a misaligned
|
|
sender by losing its mail.
|
|
|
|
## Step 3 — DKIM (the step people skip)
|
|
|
|
Admin console → Apps → Google Workspace → Gmail → **Authenticate email**.
|
|
|
|
1. **Generate new record** (2048-bit), add the TXT record it prints to DNS.
|
|
2. Wait for DNS to propagate — minutes to hours.
|
|
3. Come back and click **Start authentication**.
|
|
|
|
Until you click that last button the mail is unsigned, and unsigned mail carrying
|
|
a password link goes to spam.
|
|
|
|
## Step 4 — An App Password for Fiesta
|
|
|
|
1. Sign in as `care@nearledaily.com` → Google Account → Security → turn on
|
|
**2-Step Verification**.
|
|
2. Security → **App passwords** → create one named `Fiesta`. You get 16
|
|
characters.
|
|
3. That is what Fiesta uses. Never the account's real password.
|
|
|
|
No App passwords option? An admin has to allow it, or set up Admin console →
|
|
Gmail → Routing → **SMTP relay service** with "require SMTP authentication" and
|
|
"require TLS". In that case `MAIL_HOST` becomes `smtp-relay.gmail.com`.
|
|
|
|
## Step 5 — Point Fiesta at it
|
|
|
|
Credentials go in **`.env.secrets`**, which is read first and is the only env
|
|
file git ignores. Never in `.env` — that one is tracked and shared.
|
|
|
|
```sh
|
|
MAIL_HOST=smtp.gmail.com
|
|
MAIL_USERNAME=care@nearledaily.com
|
|
MAIL_PASSWORD=<16-character app password>
|
|
```
|
|
|
|
Already set in `.env`:
|
|
|
|
```sh
|
|
MAIL_PORT=587
|
|
MAIL_FROM=care@nearledaily.com
|
|
MAIL_FROM_NAME=Nearle
|
|
MAIL_CONSOLE_URL=https://app.nearledaily.com
|
|
```
|
|
|
|
Google shows the App Password as four groups — `abcd efgh ijkl mnop`. Paste it
|
|
with or without the spaces; Fiesta strips them for Google SMTP hosts only, and
|
|
only when what remains is the sixteen alphanumerics an App Password actually is.
|
|
Another relay's password is never edited.
|
|
|
|
`MAIL_CONSOLE_URL` is the **merchant** console and never the platform one — a
|
|
merchant sets their password at `app.nearledaily.com/set-password` and nowhere
|
|
else.
|
|
|
|
Restart. The log says which state it is in:
|
|
|
|
```
|
|
mail: sending as care@nearledaily.com via smtp.gmail.com:587
|
|
mail: OFF — <reason naming the missing variable>
|
|
```
|
|
|
|
**On a hosted deployment these belong in the platform's own environment**
|
|
(Dokploy), not in a file in the repo. A `.env` committed to the repository is
|
|
overwritten at build time — that is how the nutrition service shipped switched
|
|
off.
|
|
|
|
### What Fiesta does with them
|
|
|
|
`utils/mail.go` upgrades to TLS with STARTTLS before authenticating, and
|
|
**refuses to send at all if a relay offers no encryption while credentials are
|
|
configured**. Go's own `smtp.PlainAuth` would decline to hand over the password
|
|
anyway, so nothing leaks either way — but it reports that as the server refusing
|
|
the credentials, which sends somebody to check the password when the problem is
|
|
the connection. It also closes a downgrade, where an attacker strips STARTTLS
|
|
from the greeting.
|
|
|
|
## Step 6 — Check the DNS
|
|
|
|
```sh
|
|
dig TXT nearledaily.com +short # SPF, with _spf.google.com
|
|
dig TXT google._domainkey.nearledaily.com +short # DKIM key
|
|
dig TXT _dmarc.nearledaily.com +short # DMARC
|
|
dig MX nearledaily.com +short # smtp.google.com
|
|
```
|
|
|
|
Google's Check MX tool at toolbox.googleapps.com does the same job.
|
|
|
|
## Step 7 — Prove it end to end
|
|
|
|
Not "the config looks right" — watch one arrive.
|
|
|
|
1. Send a test to a [mail-tester.com](https://mail-tester.com) address. Aim for
|
|
9/10 or better **before** a real merchant sees one.
|
|
2. Onboard a test merchant with an address you can read.
|
|
3. Confirm it is in the **inbox, not spam**. In Gmail, "Show original" should
|
|
show SPF, DKIM and DMARC all PASS.
|
|
4. Follow the link, set a password, sign in at `app.nearledaily.com`.
|
|
5. Press **Resend invite**. It must refuse, naming the business:
|
|
*"… has already set a password — send them to the sign-in page instead."*
|
|
That refusal is what stops this becoming a password reset.
|
|
|
|
---
|
|
|
|
## Worth knowing
|
|
|
|
- **Limit:** about 2,000 messages a day per user. Onboarding runs at a few dozen
|
|
a month, so this is not a constraint.
|
|
- **Bounces** arrive as "Delivery failed" in the `care@` inbox. Nothing in Fiesta
|
|
watches for them, so somebody has to read that mailbox after onboarding.
|
|
- **Not for bulk.** Google does not permit marketing sends through Workspace. If
|
|
newsletters are ever wanted, that is a separate provider — not this mailbox.
|
|
- **If the App Password leaks:** revoke it in Google Account → Security, issue a
|
|
new one, update `.env.secrets`. Nothing else has to change.
|
|
|
|
## What the merchant receives
|
|
|
|
Plain text, deliberately. A password link arriving as an image-heavy HTML
|
|
template is the shape of a phishing mail, and plain text renders identically
|
|
everywhere. The body names the business, puts the link on its own line, and says
|
|
it expires in seven days — because an invitation found three weeks later needs to
|
|
explain itself rather than look broken.
|
|
|
|
The wording is `inviteMessage` in `services/inviteService.go`.
|