All transactional mail now sends From app.email.sender (default no-reply@origin.makepostsell.com) instead of per-shop no-reply@<domain>, with the shop name (or email.from_name) as the display name. The origin identity is DKIM-signed (d=makepostsell.com) and SPF-authorized and relays via mx1's warm IP, so operator custom-domain shops stop getting spam-foldered. format_from_header() builds the From; send_email() gained a from_name kwarg. Reply-To / per-shop contact email still TODO.
5.1 KiB
MPS-23 — Consolidated transactional sender identity + shop contact email
Status: Core shipped (2026-05-12) — single sending identity + shop display
name + DNS are live. Remaining: the Reply-To / shop-contact-email decision
(see "Open question" below), and the optional d=origin.makepostsell.com DKIM
key for author-domain-exact alignment.
Shipped
app.email.senderdefaults tono-reply@origin.makepostsell.com(overridable viaMPS_EMAIL_SENDER);app.email.from_namedefaults toMake Post Sell.lib/mail.py:format_from_header()helper;send_pyramid_email()sets the From display name torequest.shop.namewhen in shop context, elseemail.from_name.send_email()gained afrom_namekwarg.- DNS:
origin.makepostsell.com TXT "v=spf1 a a:mx1.foxhop.net -all"(inproxy.unturf.com/ingress/pdns-init.sh). opendkim on the origin already signs*@*.makepostsell.comwithd=makepostsell.com, relayed via mx1's warm IP. - Tests:
TestMailFromHeaderintest_models.py.
Problem
MPS sends each shop's transactional mail (OTP login codes, purchase receipts,
sale notifications, gift cards, offers, invites) From: no-reply@<request.domain>
— i.e. from the shop's own hostname:
my.makepostsell.com→no-reply@my.makepostsell.com<shop>.makepostsell.com→no-reply@<shop>.makepostsell.com- operator custom domain
shop.unturf.com→no-reply@shop.unturf.com
For operator custom domains, MPS controls no DKIM key for that domain and the
domain's SPF doesn't authorize MPS's sending infrastructure, so the mail is
unauthenticated (no DKIM, no SPF pass) → Gmail spam-folders it. Even
no-reply@my.makepostsell.com lands in spam on first contact because
makepostsell.com is a cold direct-signing identity (its mail historically
went through Mailgun's mg.makepostsell.com, never makepostsell.com itself).
Background: 2026-05-12 mail-infra work moved both origin boxes
(origin.makepostsell.com, origin.remarkbox.com) to DKIM-sign at origin and
relay through mx1.foxhop.net (warm IP). my.makepostsell.com / *.makepostsell.com
/ remarkbox.com mail now passes SPF+DKIM+DMARC. Operator-custom-domain shops
are the remaining gap, and even the makepostsell ones benefit from one
consistent warmed identity.
Fix
Send all MPS transactional mail from one warm, authenticated identity:
no-reply@origin.makepostsell.com.
- opendkim on
origin.makepostsell.comalready signs*@*.makepostsell.comwithd=makepostsell.com→no-reply@origin.makepostsell.comis signed (DMARC-aligned at themakepostsell.comorg-domain level under relaxed mode). origin.makepostsell.comSPF record (added inproxy.unturf.com/ingress/pdns-init.sh):v=spf1 a a:mx1.foxhop.net -all→ authorizes the origin box + the mx1 relay.- Result: every shop's mail → DKIM-signed, SPF-pass, DMARC-pass, delivered from mx1's warm IP — independent of which shop/domain it's for.
Recipient clarity: put the shop name in the display name —
From: "Acme Shop" <no-reply@origin.makepostsell.com>.
Open question — Reply-To / customer support
Shops have no contact email today (Shop model has no email/contact column).
If a customer replies to an OTP/receipt it hits no-reply@… and vanishes.
Options:
- Add
Shop.support_email(nullable) + a field inshop-settings; setReply-To: <support_email>when the operator has filled it in. Opt-in, privacy-safe. (recommended) - Catch-all
reply@origin.makepostsell.com→ forward to the shop owner's account email. Requires inbound mail handling + a mapping table. - Default
Reply-Toto the shop owner's user email. Simple, but leaks the owner's personal address.
Until decided: ship without Reply-To (honest — it's no-reply@).
Implementation
- Config:
app.email.sender = no-reply@origin.makepostsell.comindata/development.ini+ the prod pillar (foxhop-pillar/uwsgi/makepostsell/init.sls).lib/mail.py:send_pyramid_emailalready readsrequest.app.get("email.sender", …). lib/mail.py: extract_build_from_header(request, sender_email)→email.utils.formataddr((display_name, sender_email)),display_name=request.shop.namewhen a shop is in context, else the site name. AddReply-Toper the chosen option. Use the helper insend_email/send_pyramid_email.- DNS (done):
origin.makepostsell.com|TXT|v=spf1 a a:mx1.foxhop.net -all. - Optional hardening: dedicated DKIM key for
origin.makepostsell.com(publish<sel>._domainkey.origin.makepostsell.com, add to opendkim) so the signature isd=origin.makepostsell.com— author-domain-exact rather than org-aligned. Marginal deliverability gain; not required. - If option (1):
Shop.support_emailcolumn + Alembic migration (server_default '')shop-settingsform field + validation + the usual three-layer tests.
- Tests: unit (
_build_from_header,Shop.support_email), integration (shop settings save), functional (settings POST, OTP flow still works). - Docs:
docs/architecture.mdticket index,docs/design-system.mdif a new form field,CLAUDE.mdnote, portalpricing.html/index.htmlif surfaced to users.