Blog

CiviCRM Standalone: Newsletter Double Opt-in Mit Beliebiger Website

Von Johannes Filter ·

Einleitung

Wenn Sie eine Nonprofit- oder Community-Organisation betreiben, möchten Sie wahrscheinlich eine Newsletter-Anmeldung auf Ihrer Website haben. Und wenn Sie CiviCRM als CRM nutzen, sollen neue Abonnent:innen direkt in CiviCRM landen - mit sauberem Double-Opt-In und ohne zusätzlichen Newsletter-Dienst.

CiviCRM Standalone (die neue Deployment-Option, die ohne Drupal, WordPress oder Joomla als Host-CMS läuft) macht das gleichzeitig einfacher und schwieriger. Einfacher, weil das CRM jetzt ein sauber getrennter, eigenständiger Dienst ist. Schwieriger, weil die meisten vorhandenen Anleitungen davon ausgehen, dass CiviCRM in ein CMS eingebettet ist, und viele alte URL-Pfade schlicht nicht funktionieren.

Diese Anleitung zeigt, wie Sie ein Newsletter-Anmeldeformular auf jeder externen Website bauen - egal ob WordPress, statische Hugo-Seite, einfache HTML-Seite oder etwas anderes -, das über die REST API mit CiviCRM Standalone spricht. Das Formular sammelt eine E-Mail-Adresse ein, CiviCRM erstellt oder findet den Kontakt, trägt ihn in Ihre Mailing-Gruppe ein und verschickt automatisch eine Double-Opt-In-Bestätigungsmail. Ich habe mir das meiste davon auf die harte Tour zusammengesucht; hoffentlich spart Ihnen das etwas Zeit.

Voraussetzungen

CiviCRM einrichten

1. Mailing-Gruppe erstellen

Gehen Sie zu Kontakte -> Gruppen verwalten -> Neue Gruppe. Erstellen Sie eine Gruppe (z. B. “Newsletter”) und setzen Sie den Gruppentyp auf “Mailing List”.

Entscheidend: Setzen Sie die Sichtbarkeit auf “Public Pages”. Das übersieht man leicht. Die API-Action MailingEventSubscribe, die den Double-Opt-In-Ablauf auslöst, prüft, ob die Zielgruppe öffentlich ist. Wenn die Sichtbarkeit auf “User and User Admin Only” steht (der Standard), lehnt die API die Anmeldung mit einem “Group is not Public”-Fehler ab. Kein hilfreicher Hinweis, nur eine klare Absage. Also: auf “Public Pages” setzen.

2. FormProcessor konfigurieren

FormProcessor ist eine CiviCRM-Extension, mit der Sie wiederverwendbare API-Actions definieren können - im Grunde benannte Prozeduren, die mehrere Schritte bündeln und als eine einzige API-Aktion verfügbar machen. Wir erstellen eine namens newsletter_signup.

  1. Gehen Sie zu Administer -> Automation -> FormProcessors
  2. Klicken Sie auf “Add Form Processor”
  3. Nennen Sie ihn newsletter_signup (der technische Name ist wichtig - er wird zum API-Action-Namen)
  4. Fügen Sie einen Eingabeparameter hinzu: Name email, Typ “String” (oder “Email”, falls verfügbar), als erforderlich markieren
  5. Fügen Sie eine Action zum Finden oder Erstellen eines Kontakts hinzu:
    • Action-Typ: “Get or Create Contact by Email”
    • Den E-Mail-Eingabeparameter auf diese Action mappen
  6. Fügen Sie eine zweite Action für die Anmeldung hinzu:
    • Action-Typ: “MailingEventSubscribe” (oder eine generische “API Call”-Action mit Entity MailingEventSubscribe und Action create)
    • Den Parameter email auf den E-Mail-Eingabeparameter setzen
    • group_id auf die ID Ihrer Newsletter-Gruppe setzen (die Gruppen-ID finden Sie in der URL beim Bearbeiten der Gruppe oder im API Explorer)
    • contact_id aus dem Ergebnis der vorherigen “Get or Create Contact”-Action mappen

Nach dem Speichern können Sie das im CiviCRM API Explorer testen (Support -> Developer -> API Explorer v4), indem Sie FormProcessor.newsletter_signup mit einer Test-E-Mail-Adresse aufrufen. Wenn alles funktioniert, verschickt CiviCRM eine Bestätigungsmail mit Double-Opt-In-Link an diese Adresse.

3. API-Key und Site-Key finden

API-Key: Öffnen Sie den Kontakt des Benutzers, den Sie für den API-Zugriff verwenden möchten. Im Reiter API Key können Sie den Schlüssel generieren oder kopieren. Dieser Benutzer braucht ausreichende Berechtigungen, um Kontakte zu erstellen und Gruppenmitgliedschaften zu verwalten.

Site-Key: Öffnen Sie auf dem Server die Datei civicrm.settings.php Ihrer CiviCRM-Standalone-Installation und suchen Sie nach:

define('CIVICRM_SITE_KEY', 'your-site-key-here');

Das Formular bauen

Hier ist ein vollständiges, eigenständiges HTML/CSS/JS-Snippet, das Sie in jede Website einfügen können. Es spricht die CiviCRM REST API direkt aus dem Browser mit einem einfachen XMLHttpRequest an - keine Abhängigkeiten, kein Build-Schritt, kein jQuery.

Ersetzen Sie die drei Platzhalter-Variablen am Anfang: CIVICRM_URL, API_KEY und SITE_KEY.

<div id="newsletter-signup">
  <style>
    #newsletter-signup {
      max-width: 440px;
      margin: 2rem auto;
      padding: 2rem;
      background: #fff;
      border-radius: 12px;
      box-shadow: 0 2px 16px rgba(0,0,0,0.10);
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    }
    #newsletter-signup h3 {
      margin: 0 0 0.5rem 0;
      font-size: 1.25rem;
    }
    #newsletter-signup p {
      margin: 0 0 1.2rem 0;
      color: #555;
      font-size: 0.95rem;
    }
    #newsletter-signup label {
      display: block;
      font-weight: 600;
      margin-bottom: 0.4rem;
      font-size: 0.95rem;
    }
    #newsletter-signup input[type="email"] {
      width: 100%;
      padding: 0.6rem 0.8rem;
      border: 1px solid #ccc;
      border-radius: 6px;
      font-size: 1rem;
      box-sizing: border-box;
      margin-bottom: 1rem;
    }
    #newsletter-signup input[type="email"]:focus {
      outline: none;
      border-color: #c0392b;
      box-shadow: 0 0 0 2px rgba(192,57,43,0.15);
    }
    #newsletter-signup button {
      width: 100%;
      padding: 0.7rem;
      background: #c0392b;
      color: #fff;
      border: none;
      border-radius: 6px;
      font-size: 1rem;
      font-weight: 600;
      cursor: pointer;
      transition: background 0.2s;
    }
    #newsletter-signup button:hover {
      background: #a93226;
    }
    #newsletter-signup button:disabled {
      background: #ccc;
      cursor: not-allowed;
    }
    #newsletter-signup .nl-msg {
      margin-top: 1rem;
      padding: 0.8rem;
      border-radius: 6px;
      font-size: 0.95rem;
      display: none;
    }
    #newsletter-signup .nl-msg.success {
      background: #eafaf1;
      color: #1e7e34;
      border: 1px solid #b7e4c7;
      display: block;
    }
    #newsletter-signup .nl-msg.error {
      background: #fdecea;
      color: #a94442;
      border: 1px solid #f5c6cb;
      display: block;
    }
  </style>

  <h3>Newsletter abonnieren</h3>
  <p>Erhalten Sie Updates direkt in Ihr Postfach. Wir respektieren Ihre Privatsphäre.</p>

  <form id="nl-form" onsubmit="return nlSubmit(event)">
    <label for="nl-email">E-Mail-Adresse</label>
    <input type="email" id="nl-email"
           placeholder="name@beispiel.de" required>
    <button type="submit" id="nl-btn">Abonnieren</button>
  </form>
  <div id="nl-msg" class="nl-msg"></div>

  <script>
    function nlSubmit(e) {
      e.preventDefault();

      // ===== DIESE DREI WERTE ERSETZEN =====
      var CIVICRM_URL = "https://crm.example.com";
      var API_KEY     = "YOUR_API_KEY";
      var SITE_KEY    = "YOUR_SITE_KEY";
      // ======================================

      var email = document.getElementById("nl-email").value.trim();
      var btn   = document.getElementById("nl-btn");
      var msg   = document.getElementById("nl-msg");

      if (!email) return false;

      btn.disabled = true;
      btn.textContent = "Wird gesendet...";
      msg.className = "nl-msg";
      msg.style.display = "none";

      var url = CIVICRM_URL
        + "/civicrm/ajax/rest"
        + "?entity=FormProcessor"
        + "&action=newsletter_signup"
        + "&json=1"
        + "&api_key=" + encodeURIComponent(API_KEY)
        + "&key=" + encodeURIComponent(SITE_KEY)
        + "&email=" + encodeURIComponent(email);

      var xhr = new XMLHttpRequest();
      xhr.open("GET", url, true);
      xhr.onreadystatechange = function() {
        if (xhr.readyState !== 4) return;
        btn.disabled = false;
        btn.textContent = "Abonnieren";

        if (xhr.status === 200) {
          try {
            var resp = JSON.parse(xhr.responseText);
            if (resp.is_error && resp.is_error !== 0) {
              msg.textContent = "Fehler: " + (resp.error_message || "Unbekannter Fehler");
              msg.className = "nl-msg error";
            } else {
              msg.textContent = "Vielen Dank! Bitte prüfen Sie Ihr Postfach "
                + "und klicken Sie auf den Bestätigungslink, um "
                + "Ihre Anmeldung abzuschließen (Double Opt-In).";
              msg.className = "nl-msg success";
              document.getElementById("nl-email").value = "";
            }
          } catch(ex) {
            msg.textContent = "Unerwartete Antwort vom Server.";
            msg.className = "nl-msg error";
          }
        } else {
          msg.textContent = "Anfrage fehlgeschlagen (HTTP " + xhr.status
            + "). Bitte versuchen Sie es später erneut.";
          msg.className = "nl-msg error";
        }
      };
      xhr.send();
      return false;
    }
  </script>
</div>

Dieses Snippet ist vollständig eigenständig. Die Styles sind auf den Wrapper #newsletter-signup beschränkt, damit sie nicht mit den vorhandenen Styles Ihrer Website kollidieren. Es rendert eine schlichte Karte mit Schatten, abgerundeten Ecken und rotem Absende-Button.

CORS konfigurieren

Wenn Ihre Website (z. B. www.example.com) und CiviCRM (z. B. crm.example.com) auf unterschiedlichen Domains liegen, blockiert der Browser den AJAX-Request, sofern der CiviCRM-Server nicht die passenden CORS-Header sendet.

Für Apache fügen Sie Folgendes in die .htaccess im Webroot Ihrer CiviCRM-Standalone-Installation ein:

<IfModule mod_headers.c>
  Header set Access-Control-Allow-Origin "https://www.example.com"
  Header set Access-Control-Allow-Methods "GET, POST, OPTIONS"
  Header set Access-Control-Allow-Headers "Content-Type"
</IfModule>

Wenn Sie mehrere Origins erlauben müssen, brauchen Sie eine dynamischere Konfiguration mit SetEnvIf oder einer Rewrite-Regel. Für eine schnelle Entwicklungsumgebung können Sie * als Origin verwenden; in Produktion sollten Sie immer auf Ihre tatsächliche Domain einschränken.

Für nginx verwenden Sie add_header-Direktiven im Server- oder Location-Block:

location / {
    add_header Access-Control-Allow-Origin "https://www.example.com" always;
    add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
    add_header Access-Control-Allow-Headers "Content-Type" always;

    # Preflight-Requests beantworten
    if ($request_method = OPTIONS) {
        return 204;
    }
}

Starten Sie nach dem Hinzufügen der CORS-Header den Webserver neu und testen Sie erneut. Öffnen Sie die Entwicklerkonsole des Browsers und prüfen Sie, dass der Request an crm.example.com keinen CORS-Fehler mehr zeigt.

WordPress-Integration

Wenn Ihre öffentliche Website auf WordPress läuft, haben Sie je nach Theme zwei Optionen: klassisches Theme oder Block-Theme.

Option A: Contact Form 7 + CiviCRM Integration Plugin (klassische Themes)

Dieser Ansatz funktioniert gut mit klassischen Themes und gibt Ihnen ein Formular, das Sie im WordPress-Admin verwalten können.

  1. Installieren und aktivieren Sie das Plugin Contact Form 7
  2. Installieren und aktivieren Sie das Plugin “Contact Form 7 CiviCRM Integration”
  3. Erstellen Sie ein neues Contact-Form-7-Formular mit einem E-Mail-Feld
  4. Konfigurieren Sie im CiviCRM-Integration-Tab des Formulars:
    • CiviCRM Host: https://crm.example.com
    • API Key: Ihr API-Key
    • Site Key: Ihr Site-Key
    • REST Path: /civicrm/ajax/rest
  5. Setzen Sie die Action auf FormProcessor.newsletter_signup und mappen Sie das E-Mail-Feld

Wichtig: Achten Sie darauf, dass der REST-Pfad auf /civicrm/ajax/rest gesetzt ist und nicht auf den alten Pfad /sites/all/modules/civicrm/extern/rest.php oder /core/extern/rest.php. CiviCRM Standalone hat keine civicrm.config.php an der Stelle, an der extern/rest.php sie erwartet; dieser Endpunkt liefert deshalb einen HTTP-500-Fehler. Die Route /civicrm/ajax/rest läuft durch das normale CiviCRM-Routing und funktioniert korrekt mit Standalone.

Option B: HTML-Block im Block-Editor (Block-Themes)

Wenn Sie ein modernes Block-Theme verwenden (oder einfach keine zusätzlichen Plugins installieren möchten), können Sie das HTML/JS-Snippet direkt in einen Custom HTML-Block im WordPress-Block-Editor einfügen.

Das funktioniert - mit einer wichtigen Einschränkung.

WordPress entfernt <script>, <form> und <input>-Tags. WordPress hat einen Sicherheitsfilter namens kses, der HTML-Inhalte bereinigt. Wenn Sie eine Seite über den Block-Editor speichern (der intern die REST API nutzt), läuft kses und entfernt still alle Tags, die als unsicher gelten - darunter <script>, <form> und oft auch <input>. Sie fügen das Snippet ein, klicken auf Speichern, laden die Seite neu und stellen fest: Formular und JavaScript sind einfach verschwunden.

Die Lösung ist, kses-Filtering für REST-API-Requests zu deaktivieren. Legen Sie dafür ein kleines Must-Use-Plugin (mu-plugin) unter wp-content/mu-plugins/disable-kses-rest.php an:

<?php
/**
 * Plugin Name: Disable kses for REST API
 * Description: Verhindert, dass WordPress script/form/input-Tags
 *              beim Speichern via REST API entfernt (Block-Editor).
 */

add_action('rest_api_init', function() {
    if (current_user_can('unfiltered_html')) {
        kses_remove_filters();
    }
});

Das prüft, ob der aktuelle Benutzer die Berechtigung unfiltered_html hat (Administratoren haben sie standardmäßig), bevor der Filter deaktiviert wird. So können nur vertrauenswürdige Benutzer ungefiltertes HTML speichern, während die Einschränkung für niedrigere Rollen bestehen bleibt.

Gehen Sie nach dem Anlegen der Datei zurück zur Seite, fügen Sie das HTML/JS-Snippet in einen Custom-HTML-Block ein und speichern Sie. Diesmal bleiben Formular- und Script-Tags erhalten.

Integration in andere CMS / statische Websites

Das HTML/JS-Snippet von oben ist einfacher, dependency-freier Code. Es funktioniert überall, wo Sie HTML und JavaScript platzieren können. Ein paar konkrete Hinweise:

Sie brauchen nur eine Seite, die über HTTPS ausgeliefert wird (für sichere API-Aufrufe), und korrekt konfigurierte CORS-Header auf dem CiviCRM-Server, falls die Domains unterschiedlich sind.

Troubleshooting / Häufige Fallstricke

Hier sind die Probleme, über die ich gestolpert bin, damit Sie direkt zur Lösung springen können.

extern/rest.php gibt HTTP 500 zurück

Wenn Sie https://crm.example.com/core/extern/rest.php (oder irgendeinen extern/rest.php-Pfad) aufrufen und einen 500er bekommen, liegt das daran, dass CiviCRM Standalone die Datei civicrm.config.php nicht an der Stelle ablegt, an der extern/rest.php sie laden möchte. Die Lösung ist einfach: Verwenden Sie stattdessen /civicrm/ajax/rest als API-Endpunkt. Das ist die moderne, unterstützte Route für die REST API von CiviCRM Standalone.

“Group is not Public”-Fehler

Wenn Sie beim Aufruf von MailingEventSubscribe.create diesen Fehler sehen, ist die Sichtbarkeit Ihrer Ziel-Mailing-Gruppe auf “User and User Admin Only” gesetzt. Gehen Sie zu Kontakte -> Gruppen verwalten, bearbeiten Sie die Gruppe und ändern Sie die Sichtbarkeit auf “Public Pages”. Die MailingEventSubscribe-API erzwingt diese Prüfung, weil der Double-Opt-In-Ablauf für öffentliche Anmeldungen gedacht ist.

CORS-Fehler in der Browser-Konsole

Wenn Sie Fehler wie Access to XMLHttpRequest at 'https://crm.example.com/...' from origin 'https://www.example.com' has been blocked by CORS policy sehen, sendet Ihr CiviCRM-Server nicht die richtigen CORS-Header. Siehe Abschnitt CORS-Konfiguration oben. Denken Sie daran, Apache oder nginx nach dem Hinzufügen der Header neu zu starten. Beachten Sie außerdem, dass Browser CORS-Preflight-Antworten recht aggressiv cachen können - machen Sie einen Hard Refresh oder testen Sie in einem privaten Fenster.

WordPress entfernt Formular-/Script-Tags

Sie fügen das HTML-Snippet in einen Custom-HTML-Block ein, speichern, und die <script>- und <form>-Tags verschwinden. Das ist die kses-Sanitization von WordPress. Der Block-Editor speichert Inhalte über die REST API, und kses läuft bei REST-API-Requests. Installieren Sie das mu-plugin aus dem WordPress-Abschnitt oben, um kses für Admin-Benutzer bei REST-API-Requests zu deaktivieren.

API-Berechtigungsfehler bei System.get

Einige CiviCRM-API-Integrationen (z. B. das CF7-CiviCRM-Plugin) rufen System.get auf, um die Verbindung zu validieren. Diese API-Action benötigt die Berechtigung “administer CiviCRM”. Wenn Ihr API-Benutzer diese Berechtigung nicht hat, schlägt der Verbindungstest fehl - auch wenn die eigentliche Kontaktanlage und Gruppenanmeldung problemlos ohne diese Berechtigung funktionieren würden. Geben Sie dem API-Benutzer entweder die Berechtigung “administer CiviCRM”, oder - falls Ihre Security Policy das verhindert - behalten Sie im Kopf, dass die eigentliche Newsletter-Anmeldung trotzdem funktioniert; nur der Verbindungstest scheitert.


Das war’s. Wenn alles verbunden ist, läuft der Ablauf so: Besucher:in trägt E-Mail ein -> Ihr Formular ruft die REST API von CiviCRM auf -> FormProcessor erstellt/findet den Kontakt und meldet ihn an -> CiviCRM verschickt eine Double-Opt-In-Bestätigungsmail -> Besucher:in klickt den Link -> die Anmeldung ist abgeschlossen. Kein zusätzlicher Newsletter-Dienst, volle Kontrolle über Ihre Daten, und es funktioniert mit jedem Frontend.