v0.0.5: gedeelde mailboxen (info@, facturen@, nieuwsbrief@, persoonlijk) met eigen IMAP/SMTP, afzender, rol en toegang per gebruiker; inbox per mailbox en versturen vanuit een gekozen mailbox; routines (jobs.py: types met @jobs.register, interval, instellingen, logboek, slot tegen dubbel draaien; app.py scheduler/tick) voor e-mail ophalen, nieuwsbrieven versturen en opschonen; meldingen (bel, bureaubladmelding); nieuwsbrieven met segmenten, testmail, inplannen en versturen per batch over één verbinding, afmeldlink met List-Unsubscribe en bounceverwerking; oude mailinstellingen worden de eerste mailbox
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
8f6e1ab443
commit
cad838f1f4
@@ -0,0 +1,95 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}{{ c.name }}{% endblock %}
|
||||
{% block content %}
|
||||
{% set editable = c.status == 'concept' %}
|
||||
<div class="head">
|
||||
<div><a class="label" href="{{ url_for('campaigns') }}">← Nieuwsbrieven</a><h1>{{ c.name }}</h1>
|
||||
<div class="muted"><span class="chip campaign-{{ c.status }}">{{ CSTATUS.get(c.status, c.status) }}</span>
|
||||
{% if c.scheduled_at and not editable %} · vanaf {{ c.scheduled_at|dt }}{% endif %}{% if c.finished_at %} · klaar {{ c.finished_at|dt }}{% endif %}</div></div>
|
||||
<div class="actions">
|
||||
<form method="post" class="inline">
|
||||
{% if c.status in ('gepland', 'bezig') %}<button class="btn btn-outline-primary" name="action" value="pause">Pauzeren</button>{% endif %}
|
||||
{% if c.status == 'gepauzeerd' %}<button class="btn btn-primary" name="action" value="resume">Hervatten</button>{% endif %}
|
||||
{% if c.status in ('gepland', 'bezig', 'gepauzeerd') %}<button class="btn btn-outline-secondary" name="action" value="stop" onclick="return confirm('Stoppen? Wie hem nog niet kreeg, krijgt hem niet meer.')">Stoppen</button>{% endif %}
|
||||
<button class="btn btn-outline-secondary" name="action" value="copy">Kopie maken</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if not editable %}
|
||||
<div class="card"><div class="card-body">
|
||||
<h2>Voortgang</h2>
|
||||
{% set pct = (100 * (stats.totaal - stats.wacht) / stats.totaal)|round|int if stats.totaal else 0 %}
|
||||
<div class="progress mb-3" role="progressbar" aria-label="Voortgang" aria-valuenow="{{ pct }}" aria-valuemin="0" aria-valuemax="100"><div class="progress-bar" style="width: {{ pct }}%"></div></div>
|
||||
<div class="d-flex flex-wrap gap-4">
|
||||
{% for k, l in RSTATUS.items() %}<div><div class="muted">{{ l }}</div><b>{{ stats[k] }}</b></div>{% endfor %}
|
||||
</div>
|
||||
</div></div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-8">
|
||||
<form method="post" class="card compose" data-vars='{}'><div class="card-body">
|
||||
<fieldset {{ 'disabled' if not editable }}>
|
||||
<div class="row g-3">
|
||||
<div class="col-12"><label class="form-label" for="name">Naam (intern)</label><input class="form-control" id="name" name="name" value="{{ c.name }}" required></div>
|
||||
<div class="col-md-6"><label class="form-label" for="mailbox_id">Van</label>
|
||||
<select class="form-select" id="mailbox_id" name="mailbox_id">
|
||||
{% if not mailboxes %}<option value="">Geen mailbox om mee te versturen</option>{% endif %}
|
||||
{% for m in mailboxes %}<option value="{{ m.id }}" {{ 'selected' if c.mailbox_id == m.id }}>{{ m.name }}{% if m.smtp_from or m.smtp_user or m.imap_user %} · {{ m.smtp_from or m.smtp_user or m.imap_user }}{% endif %}</option>{% endfor %}
|
||||
{% if mailbox and mailbox.id not in mailboxes|map(attribute='id')|list %}<option value="{{ mailbox.id }}" selected>{{ mailbox.name }}</option>{% endif %}
|
||||
</select></div>
|
||||
<div class="col-md-6"><label class="form-label" for="segment_id">Aan (segment)</label>
|
||||
<select class="form-select" id="segment_id" name="segment_id" aria-describedby="seg-hint">
|
||||
<option value="">Kies een segment…</option>
|
||||
{% for s in segments %}<option value="{{ s.id }}" {{ 'selected' if c.segment_id == s.id }}>{{ s.name }}</option>{% endfor %}
|
||||
</select>
|
||||
<p class="hint" id="seg-hint">{% if c.segment_id and editable %}Nu {{ seg_count }} ontvanger(s). {% endif %}<a href="{{ url_for('segments') }}">Segmenten beheren</a></p></div>
|
||||
{% if templates and editable %}
|
||||
<div class="col-12"><label class="form-label" for="template">Template</label>
|
||||
<select class="form-select" id="template" name="template" autocomplete="off">
|
||||
<option value="">Geen template</option>
|
||||
{% for t in templates %}<option value="{{ t.id }}" data-subject="{{ t.subject }}" data-body="{{ t.body }}">{{ t.name }}</option>{% endfor %}
|
||||
</select></div>
|
||||
{% endif %}
|
||||
<div class="col-12"><label class="form-label" for="subject">Onderwerp</label><input class="form-control" id="subject" name="subject" value="{{ c.subject or '' }}"></div>
|
||||
<div class="col-12"><label class="form-label" for="body">Tekst</label><textarea class="form-control" id="body" name="body" rows="16">
|
||||
{{ c.body or '' }}</textarea>
|
||||
<p class="hint">Velden worden per ontvanger ingevuld: {% for k, l in FIELDS %}<code title="{{ l }}">{{ '{' ~ k ~ '}' }}</code> {% endfor %}. Zonder {afmeldlink} komt de afmeldlink onderaan.</p></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="actions">
|
||||
{% if editable %}
|
||||
<button class="btn btn-primary" name="action" value="save">Opslaan</button>
|
||||
<button class="btn btn-outline-primary" name="action" value="test">Testmail naar mij</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if editable %}
|
||||
<div class="schedule">
|
||||
<h2>Versturen</h2>
|
||||
<p class="hint">Bij inplannen worden de ontvangers uit het segment vastgelegd. Daarna verstuurt de routine "Nieuwsbrieven versturen" ze per batch.</p>
|
||||
<div class="row g-3 align-items-end">
|
||||
<div class="col-md-6"><label class="form-label" for="scheduled_at">Vanaf (leeg = zo snel mogelijk)</label><input class="form-control" id="scheduled_at" type="datetime-local" name="scheduled_at"></div>
|
||||
<div class="col-md-6"><button class="btn btn-primary" name="action" value="schedule" onclick="return confirm('Nieuwsbrief inplannen? Daarna kun je de tekst niet meer wijzigen.')">Opslaan en inplannen</button></div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div></form>
|
||||
{% if editable %}
|
||||
<form method="post" onsubmit="return confirm('Nieuwsbrief verwijderen?')"><button class="btn btn-outline-secondary btn-sm" name="action" value="delete">Nieuwsbrief verwijderen</button></form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if recipients %}
|
||||
<div class="col-lg-4">
|
||||
<div class="card"><div class="card-body">
|
||||
<h2>Ontvangers</h2>
|
||||
<ul class="timeline">{% for r in recipients %}<li>{% if r.contact_id %}<a href="{{ url_for('contact_view', cid=r.contact_id) }}">{{ r.name or r.email }}</a>{% else %}{{ r.email }}{% endif %}
|
||||
<span class="chip {{ 'verloren' if r.status in ('mislukt', 'overgeslagen') else '' }}">{{ RSTATUS.get(r.status, r.status) }}</span><br>
|
||||
<span class="muted">{{ r.email }}{% if r.sent_at %} · {{ r.sent_at|dt }}{% endif %}{% if r.error %} · {{ r.error }}{% endif %}</span></li>{% endfor %}</ul>
|
||||
{% if stats.totaal > recipients|length %}<p class="hint">De eerste {{ recipients|length }} van {{ stats.totaal }}.</p>{% endif %}
|
||||
</div></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block scripts %}{% if editable %}<script src="{{ url_for('static', filename='compose.js') }}" defer></script>{% endif %}{% endblock %}
|
||||
Reference in New Issue
Block a user