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:
Machiel
2026-10-16 18:25:44 +02:00
co-authored by Claude Opus 5.5
parent 8f6e1ab443
commit cad838f1f4
24 changed files with 1710 additions and 185 deletions
+24 -10
View File
@@ -3,41 +3,55 @@
{% block content %}
{% set sent = f == 'sent' %}
<div class="head">
<div><div class="kicker">E-mail</div><h1>{{ {'sent': 'Verzonden', 'archived': 'Archief'}.get(f, 'Inbox') }}</h1>{% if last_sync %}<div class="muted">Laatst opgehaald: {{ last_sync|dt }}</div>{% endif %}</div>
<div><div class="kicker">E-mail{% if current %} · {{ current.name }}{% endif %}</div><h1>{{ {'sent': 'Verzonden', 'archived': 'Archief'}.get(f, 'Inbox') }}</h1>
{% if current and current.last_sync %}<div class="muted">Laatst opgehaald: {{ current.last_sync|dt }}</div>{% endif %}</div>
<div class="actions">
<form class="search" method="get">
<input type="hidden" name="f" value="{{ f }}">
{% if mb %}<input type="hidden" name="mb" value="{{ mb }}">{% endif %}
<input class="form-control" type="search" name="q" value="{{ term }}" placeholder="Zoek op afzender, onderwerp, tekst…" aria-label="Zoeken in e-mail">
</form>
<a class="btn btn-outline-primary" href="{{ url_for('mail_templates') }}">Templates</a>
<form method="post" action="{{ url_for('inbox_sync') }}" class="inline"><button class="btn btn-outline-primary">Nieuwe e-mail ophalen</button></form>
<a class="btn btn-primary" href="{{ url_for('mail_new') }}">Nieuwe e-mail</a>
<form method="post" action="{{ url_for('inbox_sync') }}" class="inline">{% if mb %}<input type="hidden" name="mb" value="{{ mb }}">{% endif %}<button class="btn btn-outline-primary">Nieuwe e-mail ophalen</button></form>
<a class="btn btn-primary" href="{{ url_for('mail_new', mb=mb) }}">Nieuwe e-mail</a>
</div>
</div>
{% if not configured %}
<div class="flash">Koppel eerst je mailbox bij <a href="{{ url_for('settings') }}#mail">Instellingen</a> (IMAP voor ontvangen, SMTP voor versturen).</div>
{% if not mailboxes %}
<div class="flash">Er is nog geen mailbox voor jou. {% if g.user.role == 'admin' %}Voeg er een toe bij <a href="{{ url_for('settings') }}#mail">Instellingen</a>.{% else %}Vraag een beheerder om er een in te stellen.{% endif %}</div>
{% elif current and current.last_error %}
<div class="flash error">{{ current.name }}: laatste ophaalpoging mislukt: {{ current.last_error }}</div>
{% endif %}
{% if mailboxes|length > 1 %}
<div class="filters" aria-label="Mailboxen">
<a href="{{ url_for('inbox', f=f if f != 'all' else None, q=term or None) }}" class="{{ 'on' if not mb }}">Alle mailboxen{% if unread %} <span class="muted">{{ unread }}</span>{% endif %}</a>
{% for m in mailboxes %}
<a href="{{ url_for('inbox', mb=m.id, f=f if f != 'all' else None, q=term or None) }}" class="{{ 'on' if mb == m.id }}">{{ m.name }}{% if unread_by.get(m.id) %} <span class="muted">{{ unread_by[m.id] }}</span>{% endif %}</a>
{% endfor %}
</div>
{% endif %}
<div class="filters">
{% for k, l in [('all', 'Inbox'), ('unread', 'Ongelezen'), ('unlinked', 'Niet gekoppeld aan contact'), ('sent', 'Verzonden'), ('archived', 'Archief')] %}
<a href="{{ url_for('inbox', f=k if k != 'all' else None, q=term or None) }}" class="{{ 'on' if f == k }}">{{ l }}{% if k == 'unread' and unread %} <span class="muted">{{ unread }}</span>{% endif %}</a>
<a href="{{ url_for('inbox', f=k if k != 'all' else None, mb=mb, q=term or None) }}" class="{{ 'on' if f == k }}">{{ l }}</a>
{% endfor %}
{% if unread and not sent %}
<form method="post" action="{{ url_for('inbox_read_all') }}" class="inline"><button class="btn btn-link btn-sm">Alles als gelezen markeren</button></form>
{% if (unread_by.get(mb) if mb else unread) and not sent %}
<form method="post" action="{{ url_for('inbox_read_all') }}" class="inline">{% if mb %}<input type="hidden" name="mb" value="{{ mb }}">{% endif %}<button class="btn btn-link btn-sm">Alles als gelezen markeren</button></form>
{% endif %}
</div>
<div class="card"><div class="card-body">
{% if rows %}
<div class="table-responsive"><table class="table table-hover table-stack">
<thead><tr><th>{{ 'Aan' if sent else 'Van' }}</th><th>Onderwerp</th><th>Contact</th><th class="num">Datum</th></tr></thead>
<thead><tr><th>{{ 'Aan' if sent else 'Van' }}</th><th>Onderwerp</th><th>Contact</th>{% if not mb and mailboxes|length > 1 %}<th>Mailbox</th>{% endif %}<th class="num">Datum</th></tr></thead>
<tbody>
{% for e in rows %}
<tr class="link {{ 'unread' if not e.is_read }}" onclick="location='{{ url_for('inbox_view', eid=e.id) }}'">
<td class="stack-title">{% if not e.is_read %}<span class="dot" aria-label="Ongelezen"></span>{% endif %}{{ e.to_addr if sent else e.from_name or e.from_addr }}<a class="d-block d-md-none" href="{{ url_for('inbox_view', eid=e.id) }}">{{ e.subject }}</a></td>
<td class="d-none d-md-table-cell"><a href="{{ url_for('inbox_view', eid=e.id) }}">{{ e.subject }}</a>{% if e.attachments %} <span class="muted" title="{{ e.attachments }}">· bijlage</span>{% endif %}</td>
<td data-label="Contact">{{ e.contact_name or '' }}</td>
{% if not mb and mailboxes|length > 1 %}<td data-label="Mailbox" class="muted">{{ e.mailbox_name or '' }}</td>{% endif %}
<td data-label="Datum" class="muted num">{{ e.date|dt }}</td>
</tr>
{% endfor %}
@@ -45,5 +59,5 @@
</table></div>
{% else %}<p class="empty">{{ 'Niets gevonden.' if term else 'Geen berichten.' }}</p>{% endif %}
</div></div>
{% if f == 'archived' %}<p class="muted">Archiveren haalt een bericht alleen uit de inbox van de CRM; in je mailbox blijft het staan.</p>{% endif %}
{% if f == 'archived' %}<p class="muted">Archiveren haalt een bericht alleen uit de inbox van de CRM; in de mailbox op de server blijft het staan.</p>{% endif %}
{% endblock %}