6.9 KiB
Mail setup — Postal, 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. The DNS section below matters more than the install.
The decisions already made
| why | ||
|---|---|---|
| Server | Postal, self-hosted | open source, purpose-built for transactional mail, speaks plain SMTP so nothing in Go changes |
| 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@ |
someone replying "I never got this" is the most useful reply this system can get, and it should reach a person | |
| Outbound | relay through a smarthost at first | see Delivery |
1. Stand Postal up
Postal needs a host of its own — it wants ports 25, 80 and 443, plus MariaDB and RabbitMQ. A 2 vCPU / 4 GB box is ample for our volume.
# on the mail host
git clone https://github.com/postalserver/install /opt/postal/install
ln -s /opt/postal/install/bin/postal /usr/bin/postal
postal bootstrap postal.nearledaily.com
postal initialize
postal make-user # your admin login
postal start
Then in Postal's web UI:
- Create an organisation —
Nearle. - Add a mail server under it — call it
transactional. Keep marketing mail out of this one forever; shared reputation is the whole point. - Add the domain
nearledaily.com. Postal prints the DNS records it wants. Section 2 is those records. - Create a credential of type SMTP, scoped to that server. Postal gives you a username and password pair. This is not a mailbox login — it exists only for Fiesta to authenticate with, and it can be revoked on its own.
2. DNS on nearledaily.com
This is the part that decides whether the invitation is read or binned. Postal's domain page shows the exact values; the shapes are:
| Record | Name | Value |
|---|---|---|
| TXT (SPF) | nearledaily.com |
v=spf1 a mx include:spf.postal.nearledaily.com ~all |
| TXT (DKIM) | postal._domainkey.nearledaily.com |
the public key Postal generates |
| CNAME (Return-Path) | psrp.nearledaily.com |
rp.postal.nearledaily.com |
| TXT (DMARC) | _dmarc.nearledaily.com |
v=DMARC1; p=none; rua=mailto:care@nearledaily.com |
| PTR (rDNS) | the mail host's IP | postal.nearledaily.com — set at your VPS provider, not in DNS |
Notes that save an afternoon:
- One SPF record per domain. If
nearledaily.comalready has one, merge theinclude:into it rather than adding a second — two SPF records is a permerror and fails every check. - Start DMARC at
p=none. It reports without rejecting. Read the reports for a couple of weeks, confirm everything legitimate is aligned, then move top=quarantine. Going straight top=rejectis how you discover a misaligned sender by losing its mail. - The Return-Path CNAME is not optional. Without it, bounces go nowhere and Postal cannot tell you a merchant's address is dead — which, for this mail, is exactly the fact you most need.
- Verify with
dig TXT nearledaily.com, and send a test to a mail-tester.com address. Aim for 9/10 or better before the first real merchant.
3. 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.
# .env.secrets on the Fiesta host
MAIL_HOST=postal.nearledaily.com
MAIL_USERNAME=<from Postal's SMTP credential>
MAIL_PASSWORD=<from Postal's SMTP credential>
Everything else is already set in .env:
MAIL_PORT=587
MAIL_FROM=care@nearledaily.com
MAIL_FROM_NAME=Nearle
MAIL_CONSOLE_URL=https://app.nearledaily.com
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 and read the first log line:
mail: sending as care@nearledaily.com via postal.nearledaily.com:587
If it instead says mail: OFF — <reason>, the reason names the missing variable.
Nothing else breaks: the server boots, onboarding works, and every create answers
invited: false with that same reason on screen.
4. Prove it end to end
Not "the config looks right" — actually watch one arrive.
- Onboard a test merchant in the platform console with an address you can read.
- The success screen should say the invitation is on its way. If it says No invitation was sent, the reason on screen is the server's own.
- Open the mail. Check it is not in spam — that is the whole test.
- Follow the link, set a password, sign in at
app.nearledaily.com. - Press Resend invite on that tenant. 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.
Delivery, the hard half
Postal is the easy part. Getting mail accepted from your own IP is not:
- Most clouds block outbound port 25 by default. AWS, GCP, Azure, DigitalOcean, Oracle and Hetzner all require an exception request; some decline.
- A fresh IP has no sending reputation. Gmail and Outlook throttle or spam-folder it until it is warmed over weeks. A recycled VPS IP is frequently already on Spamhaus — check before you commit to one.
- rDNS must match the HELO hostname. Not every provider lets you set it.
So configure Postal to relay outbound through a smarthost to begin with. You keep the open-source stack, your own queue, your own logs and the freedom to move — and you borrow established IP reputation for the last hop only. Postal supports this per mail server under Settings → SMTP relays. Once you have volume and a warm dedicated IP, cut over to sending directly; nothing on the Fiesta side changes, because it only ever talks to Postal.
At our volume — a few dozen invitations a month — carrying full deliverability operations to save roughly ₹1,000 a year is a bad trade against one merchant locked out of their own business.
What the merchant actually 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 lives in inviteMessage in services/inviteService.go.