Heppi: Problem mit Header bei mail() in PHP

Hallo zusammen,

ich habe den folgenden Code und damit langsam am Verzweifeln. Wahrscheinlich stimmt irgendwas mit Content-Type nicht, aber ich komme nicht dahinter was. Wenn ich keine Anhänge an die Funktion übergebe, wird die Mail versendet, aber als Textmail - HTML wird nicht interpretiert. Wenn ich Anhänge an die Funktion übergebe, werden diese korrekt verschickt, aber die Mail bleibt ansonsten komplett leer… Jemand eine Idee, was da nicht paßt? Danke euch schon mal 😀

Schöne Grüße, Heppi.

	function sendmail($layout, $from, $to, $subject, $title, $body, $attachedfiles = NULL, $attachedfiles_name = NULL, $cc = "", $bcc = ""){
		global $newsletter_layout;

		// Text-Mail?
		if($newsletter_layout[$layout][2] == "0")
			$textmail = true;
		else
			$textmail = false;

		// Layout der Seite laden
		if($textmail){
			$ausgabe = $body;
		}
		else{
			$ausgabe = implode('', file($newsletter_layout[$layout][2]));
			$ausgabe = str_replace('<!--body-->', imap_8bit($body), $ausgabe);
			$ausgabe = str_replace('<!--title-->', $title, $ausgabe);
			$ausgabe = str_replace('<!--bilder-->', '&nbsp;', $ausgabe);
		}

		// Email zusammensetzen
		$eol = PHP_EOL;
		$id = md5(uniqid(mt_rand(), true));
		$boundary = "----".$id;

		$header = "Content-class: urn:content-classes:message".$eol;
		$header .= "User-Agent: Free WebMail".$eol;

		$header .= "From: ".$from.$eol;

		if(!empty($cc))
			$header .= "Cc: ".$cc.$eol;

		if(!empty($bcc))
			$header .= "Bcc: ".$bcc.$eol;

		$header .= "X-Priority: 3 (Normal)".$eol;
		$header .= "Importance: Normal".$eol;
		$header .= "MIME-Version: 1.0".$eol;

		if(!empty($attachedfiles))
			$header .= "Content-Type: multipart/mixed; boundary=\"".$boundary."\"".$eol;

		$message = '';

		if(!empty($attachedfiles))
			$message .= "--".$boundary.$eol;

		if($textmail)
			$message .= "Content-Type: text/plain; charset=iso-8859-1".$eol;
		else
			$message .= "Content-Type: text/html; charset=iso-8859-1".$eol;

		$message .= "Content-Transfer-Encoding: quoted-printable".$eol;
		$message .= "Content-Disposition: inline".$eol;

		$message .= $ausgabe.$eol.$eol;

		if(!empty($attachedfiles))
			$message .= "--".$boundary.$eol;

		for($x = 0;$x < count($attachedfiles);$x++){
			if(!empty($attachedfiles_name[$x])){
				$filename = $attachedfiles_name[$x];
			}
			else{
				$filename = explode('/', $attachedfiles[$x]);
				$filename = $filename[count($filename) - 1];
			}

			$file = fopen($attachedfiles[$x], "r");
			$content = fread($file, filesize($attachedfiles[$x]));
			fclose($file);
			$encodedfile = chunk_split(base64_encode($content));

			$message .= "Content-Type: application/octet-stream; name=\"".$filename."\"".$eol;
			$message .= "Content-Transfer-Encoding: base64".$eol;
			$message .= "Content-Description: ".$attachedfiles[$x].$eol;
			$message .= "Content-Disposition: attachment; filename=\"".$filename."\"".$eol.$eol;
			$message .= $encodedfile.$eol.$eol."--".$boundary;
		}

		if(!empty($attachedfiles))
			$message.="--";

		// Email verschicken
		if(mail($to, $subject, $message, $header))
			return true;
		else
			return false;
	}
  1. Ok, das erste Problem ist gelöst. Man muß wohl fragen und da kommt die Antwort von selbst 😂 Content-Type im Header hat gefehlt, wenn kein Anhang da war - ist jetzt behoben.

    Aber warum habe ich keinen Text in der Mail, wenn ich einen Anhang in der Mail habe?

    1. Tach!

      Aber warum habe ich keinen Text in der Mail, wenn ich einen Anhang in der Mail habe?

      Da stimmt wohl was mit dem Format der erzeugten Mail nicht, würde ich raten.

      Apropos raten, ganz anderer Lösungsvorschlag: Verschwende deine Zeit nicht, wenn es für das Problem bereits Lösungen gibt. Nimm zum Beispiel lieber den Swift-Mailer. Mit dem erstellst du die Mail in wenigen Zeilen.

      dedlfix.

      1. Das ganze hat ja schon funktioniert, mußte es aber umstellen wegen einer neuen PHP-Version. Bekam dann eine Fehlermeldung wegen doppelter Newlines… Habe dann Header und Message getrennt und seitdem geht es nicht mehr, obwohl Skripte im Netz das identisch machen.

        1. Liebe(r) Heppi,

          mußte es aber umstellen wegen einer neuen PHP-Version.

          also noch ein Argument für den Swiftmailer.

          Liebe Grüße,

          Felix Riesterer.

  2. Aktueller Code:

    	function sendmail($layout, $from, $to, $subject, $title, $body, $attachedfiles = NULL, $attachedfiles_name = NULL, $cc = "", $bcc = ""){
    		global $newsletter_layout;
    
    		// Text-Mail?
    		if($newsletter_layout[$layout][2] == "0")
    			$textmail = true;
    		else
    			$textmail = false;
    
    		// Layout der Seite laden
    		if($textmail){
    			$ausgabe = $body;
    		}
    		else{
    			$ausgabe = implode('', file($newsletter_layout[$layout][2]));
    			$ausgabe = str_replace('<!--body-->', imap_8bit($body), $ausgabe);
    			$ausgabe = str_replace('<!--title-->', $title, $ausgabe);
    			$ausgabe = str_replace('<!--bilder-->', '&nbsp;', $ausgabe);
    		}
    
    		// Email zusammensetzen
    		$eol = PHP_EOL;
    		$id = md5(uniqid(mt_rand(), true));
    		$boundary = "----".$id;
    
    		$header = "Content-class: urn:content-classes:message".$eol;
    		$header .= "User-Agent: Free WebMail".$eol;
    
    		$header .= "From: ".$from.$eol;
    
    		if(!empty($cc))
    			$header .= "Cc: ".$cc.$eol;
    
    		if(!empty($bcc))
    			$header .= "Bcc: ".$bcc.$eol;
    
    		$header .= "X-Priority: 3 (Normal)".$eol;
    		$header .= "Importance: Normal".$eol;
    		$header .= "MIME-Version: 1.0".$eol;
    
    		if(!empty($attachedfiles))
    			$header .= "Content-Type: multipart/mixed; boundary=\"".$boundary."\"".$eol;
    		else if($textmail)
    			$header .= "Content-Type: text/plain; charset=iso-8859-1".$eol;
    		else
    			$header .= "Content-Type: text/html; charset=iso-8859-1".$eol;
    
    		$message = '';
    
    		if(!empty($attachedfiles)){
    			$message .= "--".$boundary.$eol;
    
    			if($textmail)
    				$message .= "Content-Type: text/plain; charset=iso-8859-1".$eol;
    			else
    				$message .= "Content-Type: text/html; charset=iso-8859-1".$eol;
    
    			$message .= "Content-Transfer-Encoding: quoted-printable".$eol;
    			$message .= "Content-Disposition: inline".$eol;
    		}
    
    		$message .= $ausgabe.$eol.$eol;
    
    		if(!empty($attachedfiles))
    			$message .= "--".$boundary.$eol;
    
    		for($x = 0;$x < count($attachedfiles);$x++){
    			if(!empty($attachedfiles_name[$x])){
    				$filename = $attachedfiles_name[$x];
    			}
    			else{
    				$filename = explode('/', $attachedfiles[$x]);
    				$filename = $filename[count($filename) - 1];
    			}
    
    			$file = fopen($attachedfiles[$x], "r");
    			$content = fread($file, filesize($attachedfiles[$x]));
    			fclose($file);
    			$encodedfile = chunk_split(base64_encode($content));
    
    			$message .= "Content-Type: application/octet-stream; name=\"".$filename."\"".$eol;
    			$message .= "Content-Transfer-Encoding: base64".$eol;
    			$message .= "Content-Description: ".$attachedfiles[$x].$eol;
    			$message .= "Content-Disposition: attachment; filename=\"".$filename."\"".$eol.$eol;
    			$message .= $encodedfile.$eol.$eol."--".$boundary;
    		}
    
    		if(!empty($attachedfiles))
    			$message.="--";
    
    		// Email verschicken
    		if(mail($to, $subject, $message, $header))
    			return true;
    		else
    			return false;
    	}
    
  3. @@Heppi

    ich habe den folgenden Code

    Hattest du den per Hand in ` gepackt oder hast du den </>-Button benutzt und die Forumsoftware hat das Falsche draus gemacht? Ich hab das mal berichtigt.

    Keine Lösung deines Problems, aber:

    		// Text-Mail?
    		if($newsletter_layout[$layout][2] == "0")
    			$textmail = true;
    		else
    			$textmail = false;
    

    Sagst du zu jemanden: „Wenn das richtig ist, dann sag ‚ja‘, andernfalls sag ‚nein‘“?

    Oder sagst du: „Sag mir, ob das richtig ist“?

    Genauso in PHP o.a. Programmiersprachen: Wenn du einer Variablen den booleschen Wert einer Abfrage zuweisen willst:

    		// Text-Mail?
    		$textmail = ($newsletter_layout[$layout][2] == "0");
    

    Wobei die Klammern der besseren Lesbarkeit dienen.

    Und auch hier:

    		// Email verschicken
    		if(mail($to, $subject, $message, $header))
    			return true;
    		else
    			return false;
    

    Stattdessen:

    		// Email verschicken
    		return mail($to, $subject, $message, $header);
    

    LLAP 🖖

    --
    „Wer durch Wissen und Erfahrung der Klügere ist, der sollte nicht nachgeben. Und nicht aufgeben.“ —Kurt Weidemann
    1. Hab den </> Button benutzt. Beim zweiten Mal hat es richtig funktioniert.

      Zu dem anderen: überredet 😉

    2. @@Gunnar Bittersmann

      		// Text-Mail?
      		if($newsletter_layout[$layout][2] == "0")
      			$textmail = true;
      		else
      			$textmail = false;
      

      Übrigens: Einzelne Anweisungen in THEN- oder ELSE-Zweigen, FOR- oder WHILE-Schleifen ohne geschweifte Klammern zu notieren, gilt oft als schlechter Programmierstil. Fehleranfällig.

      Und eine Variable, die einen booleschen Wert enthält, wird üblicherweise $is… bzw. $has… benannt. Das macht den Code besser lesbar:

      		// Text-Mail?
      		$isTextmail = ($newsletter_layout[$layout][2] == "0");
      

      LLAP 🖖

      --
      „Wer durch Wissen und Erfahrung der Klügere ist, der sollte nicht nachgeben. Und nicht aufgeben.“ —Kurt Weidemann
  4. Komischwerweise kommt auch nur ein Anhang an 😕

    1. Hallo Heppi,

      zunächst mal eine Frage an alle: Ist der Funktionsname(sendmail) bedenkenlos zu nutzen? Ich habe da immer schon Bedenken(viell. grundlos), dass so ein Begriff irgendwann zu internen Konflikten führen könnte.

      Zu deinem Script. Der Aufbau erscheint mir komplizierter als wirklich nötig, zumindest tue ich mich schwer mich dort hinein zu lesen. Auch bin ich nicht sicher ob es noch zeitgemäß ist fopen, nur ISO, manuelle mime-type-Bestimmung, usw. zu nutzen. Wollte jetzt zuerst ein paar Codeschnipsel von mir zeigen, aber dachte bevor die Meute mich wegen der, mit Sicherheit vorliegenden, NoGo's zerreißt 😉, such ich mal einen passenden Link mit ähnlicher Routine raus, viell. kannst du dir was abschauen.

      Gruss
      Henry

      1. Servus Henry,

        super, das scheint zu funktionieren 😀 Allerdings habe ich jetzt noch ein kleines Problem - wahrscheinlich eher wegen ein Verständnisproblem…

        Hochgeladene Dateien werden so übergeben:

        $dateien = array($_FILES['anhang']['tmp_name'] => $_FILES['anhang']['name']);

        Jetzt habe ich mehrere Anhänge, aber nicht immer gleich viele. Diese liegen in dem folgenden Array: $_FILES['anhang']['tmp_name'][$i] mit $i 0 bis 4. Jetzt dachte ich, daß ich die einfach mit der folgenden Zeile in das Array $dateien reinpacken kann:

        $dateien[] = array($_FILES['anhang']['tmp_name'][$i] => $_FILES['anhang']['name'][$i]); Dies funktioniert aber nicht. Das hier hingegen funktioniert:

        $dateien = array($_FILES['anhang']['tmp_name'][0] => $_FILES['anhang']['name'][0], $_FILES['anhang']['tmp_name'][1] => $_FILES['anhang']['name'][1]);

        Allerdings geht das nur für eine feste Anzahl an Anhängen. Wie kann ich dem Array $dateien sonst noch einen Wert anhängen?

        1. Keine Ahnung warum das jetzt so furchtbar formatiert ist 😱

          1. Hallo Heppi,

            Keine Ahnung warum das jetzt so furchtbar formatiert ist 😱

            Es gibt eine Vorschau und einen Hilfe-Link.

            Bis demnächst
            Matthias

            --
            Rosen sind rot.
        2. Hallo Heppi,

          Was die Formatierung hier betrifft, liegt wohl(musste ich auch erst lernen) daran, dass ein Zeilenumbruch erst erzwungen wird, wenn am Satzende 2 Leerzeichen eingefügt werden. Daher, immer vorm Senden kleiner Blick in die Vorschau 😉

          Bei deinem Problem verstehe ich nicht ganz, was du erreichen möchtest, weil $_FILES gibt die ja schon alle Anhänge aus. Also gehe ich davon aus, du willst außer dem Upload noch weitere Dateien zufügen? Auch das Array zu erweitern oder anzupassen, gibt's einige Funktionen, die viell. besser geeignet sind, zb. je nach Bedarf Array_push(verändert das Ursprungsarray) oder Array_pad

          Daher… ein Fallbeispiel, was genau du erreichen willst, wäre nicht schlecht. ach ja... und

          $dateien[] = array($_FILES['anhang']['tmp_name'][$i] => $_FILES['anhang']['name'][$i]); Dies funktioniert aber nicht.

          bedeutet? Was gibt $i wirklich aus?

          Gruss
          Henry

          1. Hallo Henry,

            wieder was gelernt 😂

            Vielleicht stehe ich jetzt auch total auf dem Schlauch, aber ich habe das folgende Formular:

            <input type="file" name="anhang[0]" size=40 class="inhalt"><br>
            <input type="file" name="anhang[1]" size=40 class="inhalt"><br>
            <input type="file" name="anhang[2]" size=40 class="inhalt"><br>  
            <input type="file" name="anhang[3]" size=40 class="inhalt"><br>  
            <input type="file" name="anhang[4]" size=40 class="inhalt"><br> 
            

            Und mit $i hole ich dann die einzelnen Werte ab.
            Oder mache ich da jetzt was ganz falsch?

            Also im Prinzip habe ich ein Formular mit fünf Möglichkeiten Dateien hochzuladen und die sollen als Anhang an die Email dran.

            Danke schon mal für die Hilfe 😀

            Innerhalb des Codes funktioniert das mit der Formatierung grad nicht 😕

            Schöne Grüße, Heppi.

            1. Hallo Heppi,

              Hallo Henry,

              wieder was gelernt 😂

              Vielleicht stehe ich jetzt auch total auf dem Schlauch, aber ich habe das folgende Formular:

              <input type="file" name="anhang[0]" size=40 class="inhalt"><br>  
              <input type="file" name="anhang[1]" size=40 class="inhalt"><br>  
              <input type="file" name="anhang[2]" size=40 class="inhalt"><br>    
              <input type="file" name="anhang[3]" size=40 class="inhalt"><br>    
              <input type="file" name="anhang[4]" size=40 class="inhalt"><br>    
              
              

              Und mit $i hole ich dann die einzelnen Werte ab. Oder mache ich da jetzt was ganz falsch?

              Warum übernimmst du selbst die Indexierung der File-Arrays, es geht auch einfacher:
              ...name="anhang[]"...

              Schau mal hier

              Gruss
              Henry

              1. moin

                <input type="file" name="anhang[0]" size=40 class="inhalt"><br>  
                <input type="file" name="anhang[1]" size=40 class="inhalt"><br>  
                <input type="file" name="anhang[2]" size=40 class="inhalt"><br>    
                <input type="file" name="anhang[3]" size=40 class="inhalt"><br>    
                <input type="file" name="anhang[4]" size=40 class="inhalt"><br>    
                
                

                <input type="file" multiple> wäre deutlich einfacher. MfG

                1. Hallo pl,

                  <input type="file" multiple> wäre deutlich einfacher. MfG

                  Einfacher ja, aber zu empfehlen... hmmm weiss nicht Denn das setzt voraus, dass der jeweilige User die Möglichkeit der Mehrfachauswahl mit shift/strg kennt und die Erfahrung zeigt doch oft eher das Gegenteil. Daher bevorzuge ich Einzelfelder, sofern nicht sowieso Drag&Drop, die ich allerdings dynamisch, bei Bedarf und der Optik wegen, erweitere.

                  Gruss
                  Henry

              2. Servus Henry,

                ok, das geht natürlich auch.

                Aber wie übergebe ich die Dateien dann an die Funktion? Das hier funktioniert nicht:
                $dateien = array($_FILES['datei_feld']['tmp_name'] => $_FILES['datei_feld']['name']);

                Damit erhalte ich den folgenden Fehler:
                Warning: Illegal offset type in /www/htdocs/w00a6e0a/newsletter/emailverteiler.php on line 277

                1. Hallo Heppi,

                  ok, das geht natürlich auch.

                  nicht auch. Nur so bleibst du variabel. Es sei denn, du willst dir später die Flexibilität wieder zurück kaufen mit zb. Javascript 😉

                  Aber wie übergebe ich die Dateien dann an die Funktion? Das hier funktioniert nicht:
                  $dateien = array($_FILES['datei_feld']['tmp_name'] => $_FILES['datei_feld']['name']);

                  Damit erhalte ich den folgenden Fehler:
                  Warning: Illegal offset type in /www/htdocs/w00a6e0a/newsletter/emailverteiler.php on line 277

                  Hier scheint vorher bei den Zuweisungen/Variablen was nicht zu stimmen. Hast du alles wie beschrieben gemacht? (…//Nicht vergessen mail_att() wie oben definiert zu haben…)

                  Am Besten, poste mal das ganze Script, falls es noch nicht klappt.

                  Gruss
                  Henry

      2. Tach!

        zunächst mal eine Frage an alle: Ist der Funktionsname(sendmail) bedenkenlos zu nutzen? Ich habe da immer schon Bedenken(viell. grundlos), dass so ein Begriff irgendwann zu internen Konflikten führen könnte.

        Niemand kann in die Zukunft schauen. Vermutlich wird neben mail() keine weitere Funktion hinzukommen, E-Mail in seiner grundlegenden Form ist ein abgeschlossenes Thema. Es sein denn, man möchte Funktionalität à la SwiftMailer hinzufügen, dann wird es aber sinnvollerweise wohl etwas objektorientiertes werden. Ich würde mir da keine gesteigerten Gedanken machen. Beim Umsteigen auf neuere Versionen sollte man generell anhand der Migrationshinweise seinen Code prüfen. Hinzukommende Funktionsnamen sind nur ein Teil der möglichen Änderungen, für die man seine Scripte anpassen muss.

        dedlfix.

      3. hi Henry,

        such ich mal einen passenden Link mit ähnlicher Routine raus, viell. kannst du dir was abschauen.

        die boundary kann ein alphanumerischer String sein und zwar ohne Bindestriche: So siehst Du es viel besser (ohne abzählen und nachrechnen) ob Dein Code, der ja selbst Bindestriche hinzufügt, erwartungsgemäß funktioniert. Des Weiteren ist es unnötig für die boundary einen zufälligen Sting zu erzeugen weil das technisch völlig belanglos ist.

        Idee: Deine Mailfunktion sollte die Maildatei zurückgeben damit Kontrollausgaben (Fehlersuche!) möglich sind.

        MfG

  5. Moin,

    Grundsätzlich sind Stringverkettungen immer schlecht zu handhaben weil man dem Code einfach nicht ansehen kann wie das Ergebnis aussieht. Anstatt die Maildatei also zu versenden und jedesmal warten zu müssen bis die angekommen ist, ist es in der Phase der Entwicklung sehr zweckmäßig, sich die Datei an Ort und Stelle anzugucken bevor überhaupt ein Versand stattfindet.

    Eine solche Ausgabe kann mit dem Content Type text/plain direkt im Browser erfolgen, zumal die Maildatei ja auch im Browser erstellt wird. Mehr Möglichkeiten bietet natürlich ein Templatesystem. Dann wäre noch eine Sache:

    if($textmail)
    			$message .= "Content-Type: text/plain; charset=iso-8859-1".$eol;
    		else
    			$message .= "Content-Type: text/html; charset=iso-8859-1".$eol;
    

    diesen Ansatz halte ich für falsch, weil eine HTML Mail IMMER eine Mail vom Type multipart/mixed oder multipart/alternative sein muss, so stellt sich die Frage entweder text oder html gar nicht sondern vielmehr die Frage entweder nur text/plain oder multipart/..

    Schon hieran siehst Du, daß es einige Möglichkeiten gibt, die man mit Stringverkettungen schwerlich alle abdecken kann. Darüber hinaus stellt sich mir die Frage wie eine HTML Mail im Browser erstellt werden soll, schließlich kann man HTML Kenntnisse nicht bei jedem Anwender voraussetzen.

    Idee zur Logik also: Guck ob es Anhänge gibt, wenn ja, dann multipart/mixed ansonsten text/plain

    MfG