Scrub personal addresses from examples, tests, and intro email docs.
Real participants stay in gitignored hearth.yml/.env on the deploy host only.
This commit is contained in:
parent
9370e768c8
commit
b0db0a26e3
@ -55,7 +55,7 @@ Dry run locally without Docker:
|
||||
```bash
|
||||
python3 -m venv .venv && .venv/bin/pip install -r requirements-dev.txt
|
||||
HEARTH_CONFIG=./hearth.yml HEARTH_DB_PATH=./data/hearth.db \
|
||||
HEARTH_PASSWORD_ALERTS=... .venv/bin/python -m hearth.main --once --dry-run
|
||||
HEARTH_PASSWORD_MAILBOX_A=... .venv/bin/python -m hearth.main --once --dry-run
|
||||
```
|
||||
|
||||
Run the test suite (includes the allow-list guardrail tests):
|
||||
|
||||
@ -8,6 +8,9 @@ the individual messages themselves will just read as normal short emails.
|
||||
Don't add anyone to `hearth.yml` until they've actually seen this and said
|
||||
yes.
|
||||
|
||||
Real participant addresses live only in the gitignored `hearth.yml` on the
|
||||
deploy host — never commit them.
|
||||
|
||||
---
|
||||
|
||||
## Copy-paste template
|
||||
@ -16,10 +19,10 @@ yes.
|
||||
>
|
||||
> Hey [name],
|
||||
>
|
||||
> Random favor to ask. I run my own email server for `levkine.ca` (the
|
||||
> "IT nerd homelab" thing), and small self-hosted mail servers like mine
|
||||
> have a real problem: Gmail/Outlook/Yahoo don't trust them by default, so
|
||||
> my emails sometimes land in spam even for real messages.
|
||||
> Random favor to ask. I run my own email server (the "IT nerd homelab"
|
||||
> thing), and small self-hosted mail servers like mine have a real problem:
|
||||
> Gmail/Outlook/Yahoo don't trust them by default, so my emails sometimes
|
||||
> land in spam even for real messages.
|
||||
>
|
||||
> The fix is basically just... using it normally. The more genuine
|
||||
> back-and-forth email traffic my addresses have — people actually opening,
|
||||
@ -28,8 +31,8 @@ yes.
|
||||
>
|
||||
> So here's the ask: every so often (probably a couple times a week at
|
||||
> most, starting slower), you might get a short, casual email from one of
|
||||
> my addresses (`ilia@levkine.ca`, possibly others later). Nothing weird —
|
||||
> just a "hey, checking in, how's it going" type message. If you could:
|
||||
> my addresses. Nothing weird — just a "hey, checking in, how's it going"
|
||||
> type message. If you could:
|
||||
>
|
||||
> - **Reply normally**, like you would to a text — doesn't need to be long
|
||||
> - **Mark it "Not Spam"** if it ever lands in your Spam/Junk folder instead
|
||||
@ -42,7 +45,7 @@ yes.
|
||||
>
|
||||
> Let me know if you're in!
|
||||
>
|
||||
> — Ilia
|
||||
> — [your name]
|
||||
|
||||
---
|
||||
|
||||
@ -55,8 +58,8 @@ yes.
|
||||
text message, not a newsletter.
|
||||
- **Timing:** business hours only, your local time zone, spread out rather
|
||||
than bursts.
|
||||
- **Senders:** real mailboxes I actually control (`@levkine.ca` addresses,
|
||||
or my own personal Gmail) — never a spoofed/unrelated identity.
|
||||
- **Senders:** real mailboxes I actually control — never a spoofed/unrelated
|
||||
identity.
|
||||
- **Never:** links, attachments, requests for money/info/action, anything
|
||||
that reads like marketing.
|
||||
|
||||
|
||||
@ -31,8 +31,8 @@ HEARTH_MAX_REPLY_DELAY_MIN=180
|
||||
HEARTH_LLM_URL=
|
||||
HEARTH_LLM_MODEL=llama3.1
|
||||
|
||||
# Optional: daily summary via your existing ntfy instance
|
||||
NTFY_URL=https://push.levkin.ca
|
||||
# Optional: daily summary via ntfy
|
||||
NTFY_URL=
|
||||
NTFY_PUBLISH_TOKEN=
|
||||
NTFY_TOPIC=hearth-digest
|
||||
|
||||
@ -40,5 +40,5 @@ NTFY_TOPIC=hearth-digest
|
||||
# One HEARTH_PASSWORD_<KEY> per full_duplex participant in hearth.yml
|
||||
# (KEY uppercased, matching the participant's `key:`). send_only participants
|
||||
# need no password — Hearth never authenticates as them.
|
||||
HEARTH_PASSWORD_ILIA=
|
||||
HEARTH_PASSWORD_PERSONAL_GMAIL=
|
||||
HEARTH_PASSWORD_MAILBOX_A=
|
||||
HEARTH_PASSWORD_PERSONAL_INBOX=
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
# pairs before turning on the full pool. Omit `active` entirely (default) to
|
||||
# make everyone below active.
|
||||
#
|
||||
# active: [ilia, personal_gmail]
|
||||
# active: [mailbox_a, personal_inbox]
|
||||
#
|
||||
# tier: full_duplex
|
||||
# Your own mailboxes. Hearth logs in via IMAP+SMTP, sends new threads,
|
||||
@ -17,34 +17,34 @@
|
||||
# username + a HEARTH_PASSWORD_<KEY> env var (see env.example).
|
||||
#
|
||||
# tier: send_only
|
||||
# A friend/coworker who explicitly agreed to help warm up levkine.ca.
|
||||
# A friend/coworker who explicitly agreed to help warm up the domain.
|
||||
# Hearth only ever sends TO this address from one of your full_duplex
|
||||
# mailboxes — it never has credentials for this inbox, and never logs
|
||||
# into it. The participant replies/marks-not-spam manually, whenever
|
||||
# they feel like it, from their own mail client.
|
||||
|
||||
participants:
|
||||
ilia:
|
||||
mailbox_a:
|
||||
tier: full_duplex
|
||||
email: ilia@levkine.ca
|
||||
display_name: "Ilia"
|
||||
imap_host: mail.levkine.ca
|
||||
email: mailbox-a@example.com
|
||||
display_name: "You"
|
||||
imap_host: mail.example.com
|
||||
imap_port: 993
|
||||
smtp_host: mail.levkine.ca
|
||||
smtp_host: mail.example.com
|
||||
smtp_port: 587
|
||||
username: ilia@levkine.ca
|
||||
password_env: HEARTH_PASSWORD_ILIA
|
||||
username: mailbox-a@example.com
|
||||
password_env: HEARTH_PASSWORD_MAILBOX_A
|
||||
|
||||
personal_gmail:
|
||||
personal_inbox:
|
||||
tier: full_duplex
|
||||
email: idobkin@gmail.com
|
||||
display_name: "Ilia"
|
||||
imap_host: imap.gmail.com
|
||||
email: you@example.com
|
||||
display_name: "You"
|
||||
imap_host: imap.example.com
|
||||
imap_port: 993
|
||||
smtp_host: smtp.gmail.com
|
||||
smtp_host: smtp.example.com
|
||||
smtp_port: 587
|
||||
username: idobkin@gmail.com
|
||||
password_env: HEARTH_PASSWORD_PERSONAL_GMAIL
|
||||
username: you@example.com
|
||||
password_env: HEARTH_PASSWORD_PERSONAL_INBOX
|
||||
|
||||
# Example consenting third party — replace with a real person who has
|
||||
# actually agreed to this. Delete this entry if you only want to warm up
|
||||
|
||||
@ -9,11 +9,11 @@ VALID_YAML = textwrap.dedent(
|
||||
participants:
|
||||
alerts:
|
||||
tier: full_duplex
|
||||
email: alerts@levkine.ca
|
||||
display_name: "Ilia"
|
||||
imap_host: mail.levkine.ca
|
||||
smtp_host: mail.levkine.ca
|
||||
username: alerts@levkine.ca
|
||||
email: alerts@example.com
|
||||
display_name: "Ops"
|
||||
imap_host: mail.example.com
|
||||
smtp_host: mail.example.com
|
||||
username: alerts@example.com
|
||||
password_env: TEST_PASSWORD_ALERTS
|
||||
friend:
|
||||
tier: send_only
|
||||
@ -34,7 +34,7 @@ def test_load_valid_config(tmp_path):
|
||||
config = load_config(path)
|
||||
assert set(config.all_keys()) == {"alerts", "friend"}
|
||||
assert config.full_duplex_keys() == ["alerts"]
|
||||
assert config.is_known_email("ALERTS@levkine.ca") == "alerts"
|
||||
assert config.is_known_email("ALERTS@example.com") == "alerts"
|
||||
assert config.is_known_email("nobody@nowhere.com") is None
|
||||
|
||||
|
||||
@ -49,14 +49,14 @@ def test_duplicate_email_rejected(tmp_path):
|
||||
participants:
|
||||
alerts:
|
||||
tier: full_duplex
|
||||
email: alerts@levkine.ca
|
||||
imap_host: mail.levkine.ca
|
||||
smtp_host: mail.levkine.ca
|
||||
username: alerts@levkine.ca
|
||||
email: alerts@example.com
|
||||
imap_host: mail.example.com
|
||||
smtp_host: mail.example.com
|
||||
username: alerts@example.com
|
||||
password_env: TEST_PASSWORD_ALERTS
|
||||
duplicate:
|
||||
tier: send_only
|
||||
email: alerts@levkine.ca
|
||||
email: alerts@example.com
|
||||
display_name: "Dup"
|
||||
"""
|
||||
)
|
||||
@ -71,7 +71,7 @@ def test_full_duplex_requires_creds_fields(tmp_path):
|
||||
participants:
|
||||
broken:
|
||||
tier: full_duplex
|
||||
email: broken@levkine.ca
|
||||
email: broken@example.com
|
||||
"""
|
||||
)
|
||||
path = write_yaml(tmp_path, content)
|
||||
@ -100,10 +100,10 @@ def test_active_list_restricts_to_subset(tmp_path):
|
||||
participants:
|
||||
alerts:
|
||||
tier: full_duplex
|
||||
email: alerts@levkine.ca
|
||||
imap_host: mail.levkine.ca
|
||||
smtp_host: mail.levkine.ca
|
||||
username: alerts@levkine.ca
|
||||
email: alerts@example.com
|
||||
imap_host: mail.example.com
|
||||
smtp_host: mail.example.com
|
||||
username: alerts@example.com
|
||||
password_env: TEST_PASSWORD_ALERTS
|
||||
friend:
|
||||
tier: send_only
|
||||
@ -127,10 +127,10 @@ def test_active_list_with_unknown_key_raises(tmp_path):
|
||||
participants:
|
||||
alerts:
|
||||
tier: full_duplex
|
||||
email: alerts@levkine.ca
|
||||
imap_host: mail.levkine.ca
|
||||
smtp_host: mail.levkine.ca
|
||||
username: alerts@levkine.ca
|
||||
email: alerts@example.com
|
||||
imap_host: mail.example.com
|
||||
smtp_host: mail.example.com
|
||||
username: alerts@example.com
|
||||
password_env: TEST_PASSWORD_ALERTS
|
||||
"""
|
||||
)
|
||||
@ -146,10 +146,10 @@ def test_active_list_must_keep_a_full_duplex_participant(tmp_path):
|
||||
participants:
|
||||
alerts:
|
||||
tier: full_duplex
|
||||
email: alerts@levkine.ca
|
||||
imap_host: mail.levkine.ca
|
||||
smtp_host: mail.levkine.ca
|
||||
username: alerts@levkine.ca
|
||||
email: alerts@example.com
|
||||
imap_host: mail.example.com
|
||||
smtp_host: mail.example.com
|
||||
username: alerts@example.com
|
||||
password_env: TEST_PASSWORD_ALERTS
|
||||
friend:
|
||||
tier: send_only
|
||||
|
||||
@ -12,17 +12,17 @@ YAML = textwrap.dedent(
|
||||
participants:
|
||||
alerts:
|
||||
tier: full_duplex
|
||||
email: alerts@levkine.ca
|
||||
imap_host: mail.levkine.ca
|
||||
smtp_host: mail.levkine.ca
|
||||
username: alerts@levkine.ca
|
||||
email: alerts@example.com
|
||||
imap_host: mail.example.com
|
||||
smtp_host: mail.example.com
|
||||
username: alerts@example.com
|
||||
password_env: TEST_PASSWORD_ALERTS
|
||||
gmail:
|
||||
tier: full_duplex
|
||||
email: idobkin@gmail.com
|
||||
imap_host: imap.gmail.com
|
||||
smtp_host: smtp.gmail.com
|
||||
username: idobkin@gmail.com
|
||||
email: you@example.com
|
||||
imap_host: imap.example.com
|
||||
smtp_host: smtp.example.com
|
||||
username: you@example.com
|
||||
password_env: TEST_PASSWORD_GMAIL
|
||||
friend:
|
||||
tier: send_only
|
||||
@ -83,5 +83,5 @@ def test_cannot_send_as_send_only_participant(config):
|
||||
|
||||
def test_is_known_email_is_exact_match_only(config):
|
||||
# A lookalike/subdomain address must never resolve to a real participant.
|
||||
assert config.is_known_email("alerts@levkine.ca.evil.com") is None
|
||||
assert config.is_known_email("alerts@example.com.evil.com") is None
|
||||
assert config.is_known_email("not-friend@example.com") is None
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user