docs: add MPS-23 ticket — consolidated transactional sender identity

This commit is contained in:
russell@unturf.com 2026-05-12 11:07:38 -04:00
parent f4be4f4612
commit 0f19da5ba8
No known key found for this signature in database
2 changed files with 82 additions and 0 deletions

View file

@ -235,6 +235,7 @@ mps_page_session (raw rows)
| [MPS-20](tickets/mps-20.md) | Auction House Mode (eBay-style Bidding) | Complete |
| [MPS-21](tickets/mps-21.md) | Make-an-Offer Mode | Complete |
| [MPS-22](tickets/mps-22.md) | Kill-Switch Feature Flags — Karaoke + Torrent Off by Default | Complete |
| [MPS-23](tickets/mps-23.md) | Consolidated Transactional Sender Identity + Shop Contact Email | Open |
## Related Docs

81
docs/tickets/mps-23.md Normal file
View file

@ -0,0 +1,81 @@
# MPS-23 — Consolidated transactional sender identity + shop contact email
**Status:** Open (DNS prep done; needs a decision on the Reply-To / shop-contact-email approach before code lands)
## 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.com` already signs `*@*.makepostsell.com`
with `d=makepostsell.com``no-reply@origin.makepostsell.com` is signed
(DMARC-aligned at the `makepostsell.com` org-domain level under relaxed mode).
- `origin.makepostsell.com` SPF record (added in `proxy.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:
1. **Add `Shop.support_email`** (nullable) + a field in `shop-settings`; set
`Reply-To: <support_email>` when the operator has filled it in. Opt-in,
privacy-safe. *(recommended)*
2. Catch-all `reply@origin.makepostsell.com` → forward to the shop owner's
account email. Requires inbound mail handling + a mapping table.
3. Default `Reply-To` to 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.com` in
`data/development.ini` + the prod pillar (`foxhop-pillar/uwsgi/makepostsell/init.sls`).
`lib/mail.py:send_pyramid_email` already reads `request.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.name` when a shop is in context, else the site name. Add
`Reply-To` per the chosen option. Use the helper in `send_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 is `d=origin.makepostsell.com` — author-domain-exact rather than
org-aligned. Marginal deliverability gain; not required.
- If option (1): `Shop.support_email` column + Alembic migration (`server_default ''`)
+ `shop-settings` form 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.md` ticket index, `docs/design-system.md` if a
new form field, `CLAUDE.md` note, portal `pricing.html`/`index.html` if
surfaced to users.