inital commit
This commit is contained in:
0
server/app/mailer/examples/__init__.py
Normal file
0
server/app/mailer/examples/__init__.py
Normal file
180
server/app/mailer/examples/campaign.json
Normal file
180
server/app/mailer/examples/campaign.json
Normal file
@@ -0,0 +1,180 @@
|
||||
{
|
||||
"version": "1.0",
|
||||
"campaign": {
|
||||
"id": "rechnungslegung-2026-05",
|
||||
"name": "Rechnungslegung 2026-05",
|
||||
"mode": "draft",
|
||||
"description": "Example campaign migrated from the Java object model."
|
||||
},
|
||||
"fields": [
|
||||
{
|
||||
"name": "monthyear",
|
||||
"type": "string",
|
||||
"label": "Month/year",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "number",
|
||||
"type": "string",
|
||||
"label": "Dienststelle",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "anrede",
|
||||
"type": "string",
|
||||
"label": "Salutation"
|
||||
},
|
||||
{
|
||||
"name": "zip_password",
|
||||
"type": "password",
|
||||
"label": "ZIP password",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"global_values": {
|
||||
"monthyear": "05 / 2026"
|
||||
},
|
||||
"server": {
|
||||
"smtp": {
|
||||
"host": "smtp.example.org",
|
||||
"port": 587,
|
||||
"username": "user@example.org",
|
||||
"password": "CHANGE_ME_OR_REFERENCE_SECRET",
|
||||
"security": "starttls"
|
||||
},
|
||||
"imap": {
|
||||
"enabled": true,
|
||||
"host": "imap.example.org",
|
||||
"port": 993,
|
||||
"username": "user@example.org",
|
||||
"password": "CHANGE_ME_OR_REFERENCE_SECRET",
|
||||
"security": "tls",
|
||||
"sent_folder": "auto"
|
||||
}
|
||||
},
|
||||
"recipients": {
|
||||
"from": {
|
||||
"name": "Rechnungslegung D5",
|
||||
"email": "d5-rechnungslegung@example.org",
|
||||
"type": "to"
|
||||
},
|
||||
"allow_individual_from": false,
|
||||
"to": [],
|
||||
"allow_individual_to": true,
|
||||
"cc": [],
|
||||
"allow_individual_cc": false,
|
||||
"bcc": [],
|
||||
"allow_individual_bcc": false,
|
||||
"reply_to": [
|
||||
{
|
||||
"name": "Rechnungslegung D5",
|
||||
"email": "d5-rechnungslegung@example.org",
|
||||
"type": "reply_to"
|
||||
}
|
||||
],
|
||||
"allow_individual_reply_to": false,
|
||||
"bounce_to": [],
|
||||
"allow_individual_bounce_to": false,
|
||||
"disposition_notification_to": [],
|
||||
"allow_individual_disposition_notification_to": false
|
||||
},
|
||||
"template": {
|
||||
"subject": "Rechnungslegungslisten für ${global::monthyear} und Dienststelle ${local::number}",
|
||||
"text": "${local::anrede},\n\nbeigefügt erhalten Sie die Rechnungslegungslisten für ${global::monthyear}.\n\nMit freundlichen Grüßen"
|
||||
},
|
||||
"attachments": {
|
||||
"base_path": "./data/attachments",
|
||||
"allow_individual": true,
|
||||
"send_without_attachments": false,
|
||||
"global": [],
|
||||
"missing_behavior": "ask",
|
||||
"ambiguous_behavior": "ask"
|
||||
},
|
||||
"entries": {
|
||||
"source": {
|
||||
"type": "csv",
|
||||
"path": "./data/recipients.csv",
|
||||
"delimiter": ";",
|
||||
"encoding": "utf-8",
|
||||
"has_header": true
|
||||
},
|
||||
"mapping": {
|
||||
"id": "ID",
|
||||
"active": "Aktiv",
|
||||
"to.0.email": "E-Mail",
|
||||
"to.0.name": "Name",
|
||||
"fields.number": "Dienststelle",
|
||||
"fields.anrede": "Anrede",
|
||||
"fields.zip_password": "ZIP-Passwort",
|
||||
"attachments.0.base_dir": "Unterordner",
|
||||
"attachments.0.file_filter": "Dateimuster"
|
||||
},
|
||||
"defaults": {
|
||||
"active": true,
|
||||
"to": [],
|
||||
"combine_to": false,
|
||||
"cc": [],
|
||||
"combine_cc": true,
|
||||
"bcc": [],
|
||||
"combine_bcc": true,
|
||||
"reply_to": [],
|
||||
"combine_reply_to": true,
|
||||
"bounce_to": [],
|
||||
"combine_bounce_to": true,
|
||||
"disposition_notification_to": [],
|
||||
"combine_disposition_notification_to": true,
|
||||
"attachments": [
|
||||
{
|
||||
"id": "individual-documents",
|
||||
"label": "Personalized PDF bundle",
|
||||
"base_dir": ".",
|
||||
"file_filter": "${local::number}_*.pdf",
|
||||
"include_subdirs": false,
|
||||
"required": true,
|
||||
"allow_multiple": true,
|
||||
"missing_behavior": "ask",
|
||||
"ambiguous_behavior": "continue",
|
||||
"zip": {
|
||||
"enabled": true,
|
||||
"filename_template": "Rechnungslegung_${local::number}.zip",
|
||||
"password_template": "${local::zip_password}",
|
||||
"method": "aes"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combine_attachments": true,
|
||||
"fields": {}
|
||||
}
|
||||
},
|
||||
"validation_policy": {
|
||||
"missing_required_attachment": "ask",
|
||||
"missing_optional_attachment": "warn",
|
||||
"ambiguous_attachment_match": "ask",
|
||||
"missing_email": "block",
|
||||
"template_error": "block",
|
||||
"inactive_entry": "drop"
|
||||
},
|
||||
"delivery": {
|
||||
"rate_limit": {
|
||||
"messages_per_minute": 5,
|
||||
"concurrency": 1
|
||||
},
|
||||
"imap_append_sent": {
|
||||
"enabled": true,
|
||||
"folder": "auto"
|
||||
},
|
||||
"retry": {
|
||||
"max_attempts": 3,
|
||||
"backoff_seconds": [
|
||||
60,
|
||||
300,
|
||||
900
|
||||
]
|
||||
}
|
||||
},
|
||||
"status_tracking": {
|
||||
"enabled": true,
|
||||
"initial_build_status": "built",
|
||||
"initial_send_status": "draft"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
dummy example attachment for resolver smoke tests
|
||||
2
server/app/mailer/examples/data/recipients.csv
Normal file
2
server/app/mailer/examples/data/recipients.csv
Normal file
@@ -0,0 +1,2 @@
|
||||
ID;Aktiv;E-Mail;Name;Dienststelle;Anrede;ZIP-Passwort;Unterordner;Dateimuster
|
||||
entry-001;true;mail@example.com;Example Recipient;ab0000;Sehr geehrte Damen und Herren;secret-demo;xls;ab????-123456-*.XLSX
|
||||
|
73
server/app/mailer/examples/rechnungslegung_2026_05.py
Normal file
73
server/app/mailer/examples/rechnungslegung_2026_05.py
Normal file
@@ -0,0 +1,73 @@
|
||||
"""Python port of the provided Java MultiMailerSettings example.
|
||||
|
||||
This is intentionally safe: credentials and real recipients are placeholders.
|
||||
Run from server/ with:
|
||||
python -m app.mailer.examples.rechnungslegung_2026_05
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
from app.mailer.domain.campaign import MailAttachmentConfig, MailCampaign, MailServerSettings
|
||||
from app.mailer.domain.fields import FieldType
|
||||
from app.mailer.domain.recipients import Recipient
|
||||
from app.mailer.services.campaign_executor import build_mail_queue
|
||||
|
||||
|
||||
def build_campaign() -> MailCampaign:
|
||||
mail_settings = MailServerSettings(
|
||||
server="smtp.example.org",
|
||||
username="user@example.org",
|
||||
password="change-me",
|
||||
).use_starttls()
|
||||
|
||||
campaign = MailCampaign.with_server_settings(mail_settings)
|
||||
campaign.set_from(Recipient(address="d5-rechnungslegung@example.org", name="Rechnungslegung D5"))
|
||||
campaign.allow_individual_to()
|
||||
campaign.allow_individual_attachments()
|
||||
campaign.dont_send_without_attachments()
|
||||
campaign.base_attachment_path = Path("/mnt/FLASH/rele/202606")
|
||||
|
||||
campaign.add_field("monthyear", FieldType.STRING)
|
||||
campaign.add_field("number", FieldType.STRING)
|
||||
campaign.add_field("password", FieldType.PASSWORD)
|
||||
campaign.add_field("anrede", FieldType.STRING)
|
||||
|
||||
campaign.set_field_content_for_name("monthyear", "05 / 2026")
|
||||
campaign.subject_template.set_template_string(
|
||||
"Rechnungslegungslisten für ${global::monthyear} und Dienststelle ${local::number}"
|
||||
)
|
||||
campaign.mail_template.set_template_string(
|
||||
"${local::anrede},\r\n\r\n"
|
||||
"in der Anlage erhalten Sie die Rechnungslegungslisten für die Dienststelle "
|
||||
"${local::number} für den Abrechnungsmonat ${global::monthyear} im Excel-Format. "
|
||||
"Bitte verwenden Sie zum öffnen das dauerhafte Passwort, das Ihnen bereits in der Vergangenheit zugeschickt wurde.\r\n"
|
||||
"Die Rechnungslegungslisten liefern den Nachweis (inkl. Brutto-/Netto-Darstellung) "
|
||||
"der auf Ihren dezentral bewirtschafteten Fonds gebuchten Personalkosten. Sie dienen der "
|
||||
"Überwachung und Kontrolle und ggf. als Nachweis gegenüber Drittmittelgebern.\r\n"
|
||||
"Die Listen erhalten vertrauliche personenbezogene Daten, daher sind diese nur berechtigten "
|
||||
"Personen zugänglich zu machen und nur für einen unbedingt notwendigen Zeitraum aufzubewahren.\r\n"
|
||||
"Falls Sie Rechnungslegungslisten erhalten haben sollten, die nicht zu Ihrer Einrichtung gehören, "
|
||||
"bitten wir Sie um entsprechende Rückmeldung.\r\n\r\n"
|
||||
"Mit freundlichen Grüßen\r\n\r\n"
|
||||
"Rechnungslegungsteam Dezernat 5"
|
||||
)
|
||||
|
||||
campaign.add_new_mail_entry() \
|
||||
.add_to(Recipient(address="mail@example.com", name="mail@example.com")) \
|
||||
.no_combine_to() \
|
||||
.combine_attachments_with_global() \
|
||||
.add_mail_attachment_config(MailAttachmentConfig(Path("xls/"), "ab????-123456-*.XLSX", False)) \
|
||||
.set_field_content_for_name("number", "ab0000") \
|
||||
.set_field_content_for_name("password", b"..........") \
|
||||
.set_field_content_for_name("anrede", "Sehr geehrte Damen und Herren")
|
||||
|
||||
return campaign
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
mc = build_campaign()
|
||||
queue = build_mail_queue(mc, zip_attachments=False)
|
||||
print(f"Built {queue.mail_count} message(s).")
|
||||
for message in queue:
|
||||
print("---")
|
||||
print("To:", message.get("To"))
|
||||
print("Subject:", message.get("Subject"))
|
||||
Reference in New Issue
Block a user