Motivation

Die meisten kostenlosen Mail-Server lassen nur E-Mails an eine begrenzte Anzahl von BCC-Adressen pro Tag zu. Wegen des einzuhaltenden Datenschutzes ist es nicht erlaubt, eine Mail an viele Empfänger mit offenen Adressen zu senden, weil dann alle Empfänger die E-Mail-Adressen aller Empfänger sehen würden.

Das mit Visual Studio in C# geschriebene Programm SendMail sendet eine E-Mail jeweils einzeln an eine beliebig große Anzahl von Empfängern.

In­halts­ver­zeich­nis

Download

Prinzip

Verwendung

Konfigurationsdatei

Adressenliste

Bedienung

Implementierung

Klasse Main

Einlesen der Konfigurationsdaten

Einlesen der Adressenliste

Übernahme des E-Mail-Inhalts

Versenden der E-Mails

Klasse SendMailConfig

Klasse MailSender

Download

Download VS 2015 Projekt
VS 2022
Projekt SendMail
  Download Binary
Binary

Prinzip

Eine Datei mit E-Mail-Adressen wird eingelesen und eine in einem beliebigen E-Mail-Programm entworfene Mail im HTML-Format wird über die Zwischenablage (Clipboard) in das Programm eingefügt. SendMail lauscht auf die Einfügung eines HTML-Dokuments in die Zwischenablage und übernimmt dies automatisch. Beim Senden wird jeder der eingelesenen E-Mail-Adressen eine eigene E-Mail gesendet. Zwischen dem Versand zweier E-Mail wird eine Pause eingefügt, für den Fall dass der Mail-Server es nicht mag, wenn Mails mit hoher Frequenz gesendet werden.

Verwendung

Konfigurationsdatei

Für das Programm muss eine Textdatei mit folgenden Einträgen in der angegeben Reihenfolge hinterlegt sein:

Zeile Bedeutung Beispiel
1 Pfad und Name der Datei mit der Liste der E-Mail-Adressen der Empfänger C:\Users\ulli\Documents\Mail\Adressen.txt
2 Pfad und Name der Datei in die die Fehlermeldungen ausgegeben werden sollen C:\Users\ulli\Documents\Mail\Fehler.txt
3 Name oder die IP-Adresse des Hostcomputers, der für SMTP-Transaktionen verwendet wird. mail.gmx.net
4 Der Benutzername zur Anmeldung beim Host, meist die E-Mail-Adresse des Absenders ulli@gmx.de
5 Das dazugehörige Passwort geheim
6 Der mit der Adresse verbundenen Anzeigename Ullis Roboter Seite
7 E-Mail-Adresse, an die eine Test-E-Mail gesendet werden soll ulli@web.de
8 Zeitverzögerung zwischen dem Versenden zweier E-Mails in Millisekunden 1000

Die Datei mit den obigen Beispieldaten sähe so aus:

C:\Users\ulli\Documents\Mail\Adressen.txt
C:\Users\ulli\Documents\Mail\Fehler.txt
mail.gmx.net
ulli@gmx.de
geheim
Ullis Roboter Seite
ulli@web.de
1000

Es gibt folgende Möglichkeiten, dem Programm die Konfigurationsdatei zuzuweisen.

  1. Datei mit dem Namen SendMailConfig.txt im Ordner der .exe-Datei. Wenn keine besonderen Angeben gemacht werden, ist das Verzeichnis mir der .exe-Datei das Arbeitsverzeichnis.
  2. Wenn man das Programm über eine Verknüpfung (Link) startet, hat man die Möglichkeit das Arbeitsverzeichnis und damit den Ort der Konfigurationsdatei SendMailConfig.txt festzulegen:

    Verknüpfung zu SendMail
  3. Der Dateiname der Konfigurationsdatei kann auch als Kommandozeilenparameter angegeben werden, z.B. ebenfalls über eine Verknüpfung oder über eine Batch-Datei.

    SendMail mit Kommmandozeilenparameter starten

Der Kommandozeilenparameter wird als erstes ausgewertet. Wurde keiner angegeben, wird versucht die Datei SendMailConfig.txt aus dem Arbeitsverzeichnis zu laden.

Adressenliste

Die Adressenliste ist eine einfache Textdatei mit einer E-Mail-Adresse pro Zeile. Zeilen, die mit # (Rautezeichen) beginnen, werden ignoriert:

Aufbau der Adressenliste

Bedienung

Nach dem Programmstart werden die Konfigurationsdatei und die Datei mit der E-Mail-Adressenliste eingelesen. Tritt hierbei ein Fehler auf, wird das Programm mit einer entsprechenden Meldung abgebrochen. Wenn alles korrekt eingelesen wurde, erscheint das folgende Fenster und das Programm wartet darauf, dass ein HTML-Dokument in die Zwischenablage eingefügt wird.

Startdialog

Am besten entwirft man die E-Mail in einem E-Mail-Programm wie Thunderbird im HTML-Format. Das Einfügen von Bildern ist möglich. Ist die E-Mail fertiggestellt und SendMail gestartet, markiert man den gesamten E-Mail-Inhalt (Strg+A) und kopiert ihn in die Zwischenablage (Strg+C). Nun werden der E-Mail-Text und die eingefügten Bilder übernommen. Die Übernahme wird durch ein Tonsignal quittiert.

Der Betreff kann über die Tastatur eingegeben oder hineinkopiert werden.

Startdialog mit übernommenen Daten

 Wenn etwas Falsches übernommen wurde oder der Text noch einmal korrigiert werden muss, muss der bereits eingefügte Text vorher gelöscht werden. Der E-Mail-Inhalt wird nicht automatisch überschrieben.

Anschließend sollte die E-Mail an die Test-E-Mail-Adresse versendet werden (Schaltfläche Test). So lässt sich überprüfen, wie die E-Mail bei den Empfängern angezeigt wird.

Wenn die E-Mail wie vorgesehen angezeigt wird, kann sie an alle E-Mail-Adressen in der Adressenliste versendet werden (Schaltfläche Senden).

Die E-Mail-Adresse, an die die E-Mail erfolgreich versendet wurde, wird in die Liste Gesendet übertragen. Tritt beim Versenden ein Fehler auf, beispielsweise weil keine Verbindung zum Mail-Server hergestellt werden kann, wird die entsprechende E-Mail-Adresse in die Liste Fehler übernommen. Oberhalb beider Listen wird die Anzahl der Elemente angezeigt.

Fehlermeldungen wie Empfänger nicht gefunden oder Empfängerpostfach voll werden beim Versenden nicht vom Mail-Server zurückgemeldet. Dadurch scheint die E-Mail erfolgreich versendet worden zu sein. Der Mail-Server sendet aber in diesen Fehler eine Fehlermeldung per E-Mail an die Absender-Adresse.

Es kann versucht werden, die E-Mail an die Adressen, bei denen ein Fehler aufgetreten ist, ein weiteres Mal zu versenden. Über die Schaltfläche Fehler wiederholen werden die Adressen aus der Fehlerliste in die Versandliste übertragen.

Manche Mail-Server mögen es nicht, wenn E-Mails in hoher Frequenz versendet werden. Nach einer gewissen Anzahl nehmen sie dann keine neuen Mails mehr an. Deshalb wird zwischen dem Versand zweier E-Mails eine kurze Pause eingefügt. Wie lange diese sein soll, kann über die Konfigurationsdatei festgelegt werden. Die optimale Pausendauer muss individuell ausprobiert werden.

Implementierung

Klasse Main

Das Formular Main ist das einzige Formular dieses Programm. Es liest die Konfigurationsdatei und die Adressliste ein. Es steuert den Versand der E-Mails.

Einlesen der Konfigurationsdaten

Die Konfigurationsdaten werden bereits im Konstruktor eingelesen:

readonly bool configLoaded = false;

// ...

internal Main() {
   InitializeComponent();

   string[] args = Environment.GetCommandLineArgs();
   if (args.Length > 1) {
      fnConfig = args[1];
   }

   try {
      sendMailConfig = SendMailConfig.FromFile(fnConfig);
      configLoaded = true;
   }
   catch (Exception) {
      MessageBox.Show("Konfiguarationsdatei '" + fnConfig + "' kann nicht eingelesen werden.",
         "SendMail", MessageBoxButtons.OK, MessageBoxIcon.Error);
   }
}

Kann die Datei nicht gefunden werden, bricht das Programm mit einer Fehlermeldung ab. Der Inhalt der Konfigurationsdatei wird nicht überprüft.

Einlesen der Adressenliste

Die E-Mail-Adressenliste wird im Ereignis Form_Load eingelesen:

private void Form1_Load(object sender, EventArgs e) {
   if (!configLoaded) { // Programmabbruch: Keine Konfiguration geladen
      Close();
      return;
   }

   NativeMethods.AddClipboardFormatListener(Handle);
   string[] allAddresses;
   try {
      allAddresses = File.ReadAllText(sendMailConfig.addressListFilename).Split("\r\n");
   }
   catch (Exception) {
      MessageBox.Show("Adressenliste '" + sendMailConfig.addressListFilename + "' kann nicht eingelesen werden.",
         "SendMail", MessageBoxButtons.OK, MessageBoxIcon.Error);
      Close();
      return;
   }
// ...

Übernahme des E-Mail-Inhalts

Der E-Mail-Inhalt wird in einem Steuerelement vom Typ WebBrowser angezeigt. Dieses Steuerelement ist in der Lage HTML-Dokumente anzuzeigen. Leider können die Inhalte nicht einfach geändert werden.

Die automatische Übernahme des E-Mail-Inhalts erfolgt durch eine überschriebene Fensterfunktion WndProc:

protected override void WndProc(ref Message m) {
   const int WM_CLIPBOARDUPDATE = 0x31D;
   if (wbMailDisplay.DocumentText is null || wbMailDisplay.DocumentText == "") {
      if (m.Msg == WM_CLIPBOARDUPDATE) {
         string clipboardData = Clipboard.GetText(TextDataFormat.Html);
         if (!string.IsNullOrEmpty(clipboardData)) {
            int index = clipboardData.IndexOf("<html");
            if (index >= 0) {
               clipboardData = clipboardData.Substring(index);
               wbMailDisplay.DocumentText = clipboardData;
               Console.Beep();
            }
         }
      }
   }
   base.WndProc(ref m);
}

Die Nachricht vom Typ WM_CLIPBOARDUPDATE wird abgefangen und darauf untersucht, ob die Zwischenablage ein HTML-Dokument enthält. Der vom E-Mail-Programm gelieferte Mail-Code enthält meist noch einen Vorspann. Der HTML-Teil isoliert und an das WebBrowser-Steuerelement übergeben.

Das Überschreiben eines bereits eigelesenen Inhalts wird verhindert.

Versenden der E-Mails

Um ein versehentliches Drücken der Schaltfläche Senden abzufangen, wird zunächst ein Dialog angezeigt, der zum Senden bestätigt werden muss.

Bestätigungsabfrage

Danach wird für jede eingelesene (in der Listbox lbToSend) Adresse die Methode MailSender.Send zum Versenden der Mail aufgerufen.

DialogResult dialogResult = MessageBox.Show("Mail jetzt senden?", "Mail versenden", MessageBoxButtons.YesNo);
if (dialogResult == DialogResult.Yes) {
   foreach (string recipient in lbToSend.Items) {
      if (recipient != "") {
         Exception? ex = MailSender.Send(recipient, tbSubject.Text, wbMailDisplay.DocumentText, sendMailConfig);
         if (ex != null) {
            // ... hier erfolgt die Fehlerbehanlung
            // ... Schreiben in die Fehlerdatei
            // ... Adresse in ListBox lbError übertragen
         }
         else {
            // ... Versand erfolgreich
            // ... Adresse in ListBox lbSend übertragen
         }
      }
      // Frequenz herab setzen
      Thread.Sleep(10000);
   }
   MessageBox.Show("Fertig!");
}

Klasse SendMailConfig

Die Klasse SendMailConfig dient zum Einlesen und Speichern der Konfigurationsdaten (siehe Abschnitt Konfiguration).

Member der Klasse SendMailConfig

Klasse MailSender

Die Klasse MailSender stellt die statische Methode Send zum Versenden von E-Mails bereit. Send benutzt eine SmtpClient-Instanz zum versenden der Mail. Nach und nach werden die zum Versand notwendigen Objekte erstellt und schließlich versendet.