Membership: newsletter opt-in at signup and on the profile

An unticked checkbox on registration subscribes the verified address to
the listmonk list (preconfirmed) once the account exists. The profile
page gets a subscribe/unsubscribe toggle, and verified email changes
follow through to listmonk. Newsletter outages never block accounts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-06 20:27:40 -05:00
co-authored by Claude Opus 5.5
parent 5954ec1061
commit 98e09627ed
10 changed files with 463 additions and 5 deletions
+19 -1
View File
@@ -43,7 +43,7 @@
</section>
{% else %}
<nav class="profile-jump-links" aria-label="Profile sections">
<a href="#identity">Identity</a><a href="#contact">Contact</a><a href="#addresses">Addresses</a><a href="#security">SSH keys</a><a href="#email">Email</a><a href="#password">Password</a>
<a href="#identity">Identity</a><a href="#contact">Contact</a><a href="#addresses">Addresses</a><a href="#security">SSH keys</a><a href="#email">Email</a>{% if newsletter_status %}<a href="#newsletter">Newsletter</a>{% endif %}<a href="#password">Password</a>
</nav>
<form action="{% url 'members:profile' %}" method="post" enctype="multipart/form-data" class="profile-edit-form">
{% csrf_token %}
@@ -102,6 +102,24 @@
<button class="button button-primary" type="submit">Send verification email</button>
</form>
</section>
{% if newsletter_status %}
<section id="newsletter" class="member-panel profile-section" aria-labelledby="newsletter-heading">
<h2 id="newsletter-heading">Newsletter</h2>
{% if request.GET.newsletter == "saved" %}<p class="member-notice" role="status">Your newsletter preference has been saved.</p>{% elif request.GET.newsletter == "error" %}<p class="profile-errors" role="alert">Your newsletter preference couldn't be changed just now. Please try again later.</p>{% endif %}
<p>One short email per premiere (about twice a month), plus the monthly essay. Sent to {{ account.email }}.</p>
{% if newsletter_status == "subscribed" %}
<p>You're subscribed.</p>
<form action="{% url 'members:newsletter' %}" method="post">{% csrf_token %}<input type="hidden" name="action" value="unsubscribe"><button class="button button-secondary" type="submit">Unsubscribe</button></form>
{% elif newsletter_status == "unsubscribed" %}
<p>You're not subscribed.</p>
<form action="{% url 'members:newsletter' %}" method="post">{% csrf_token %}<input type="hidden" name="action" value="subscribe"><button class="button button-primary" type="submit">Subscribe</button></form>
{% elif newsletter_status == "blocked" %}
<p>This address has opted out of all eom.dev email. Contact eric@eom.dev to opt back in.</p>
{% else %}
<p>Newsletter settings are temporarily unavailable.</p>
{% endif %}
</section>
{% endif %}
<section id="password" class="member-panel profile-section" aria-labelledby="password-heading">
<h2 id="password-heading">Password</h2>
<p>Choose a strong, unique password for your shared eom.dev account. Your current password is needed to authorize the change. Spaces in passwords are preserved.</p>