MailRudi

API-First Newsletter-Delivery-Plattform

Was ist MailRudi?

MailRudi ist eine minimale, API-first Newsletter-Delivery-Plattform, die AWS Simple Email Service (SES) nutzt. Entwickelt für Entwickler, die Newsletter-Versand direkt in ihre Anwendungen integrieren möchten.

Was kann MailRudi?

  • Newsletter versenden – Erstelle und versende Kampagnen per API
  • Previews generieren – Vorschau vor dem Versand ohne E-Mail zu senden
  • Job-Status tracken – Überwache den Versand-Status in Echtzeit
  • Multi-Tenant – Isolierte API-Keys für verschiedene Nutzer
  • Rate Limiting – Automatische Respektierung von SES-Limits

Wie funktioniert MailRudi?

1. API Request

Client sendet POST-Request an /v1/campaigns mit API-Key

2. Campaign Creation

API-Service erstellt Campaign in der Datenbank

3. Job Queue

Job wird in SQS-Queue (oder lokale DB-Queue) eingereiht

4. Worker Processing

Worker-Prozess verarbeitet Jobs asynchron und versendet via AWS SES

5. Status Updates

Status kann per GET /v1/jobs/{id} abgefragt werden

Platzhalter

Email-Inhalte unterstützen dynamische Platzhalter im Format {{platzhalter}}, die für jeden Empfänger individuell ersetzt werden.

  • Standard-Platzhalter: {{firstname}}, {{lastname}}, {{name}}
  • Benutzerdefinierte Platzhalter: Beliebige Felder aus attributes (z.B. {{Alter}}, {{Stadt}})
  • Verwendung: In content.subject, content.html und content.text

Recipient-Felder (alle optional außer email):

  • email - Pflicht - E-Mail-Adresse
  • firstname - Optional - Vorname
  • lastname - Optional - Nachname
  • name - Optional - Vollständiger Name (Fallback)
  • attributes - Optional - Benutzerdefinierte Felder als Key-Value-Paare

API-First Approach

MailRudi wurde vollständig API-first entwickelt:

  • Keine GUI erforderlich – Alles über REST API steuerbar
  • Direkte Integration – Einfach in bestehende Anwendungen einbinden
  • Standard HTTP – Nutzt bewährte HTTP-Methoden und Status-Codes
  • JSON-basiert – Einfache, strukturierte Datenformate
  • Skalierbar – API-Service und Worker getrennt skalierbar

Beispiel: Newsletter versenden

POST /v1/campaigns
X-Api-Key: your-api-key
Content-Type: application/json

{
  "action": "send",
  "sender": {
    "from_localpart": "newsletter",
    "from_name": "Team"
  },
  "recipients": {
    "inline": [
      {
        "email": "user@example.com",
        "firstname": "John",
        "lastname": "Doe",
        "attributes": {
          "Alter": "25"
        }
      }
    ]
  },
  "content": {
    "subject": "Hello {{firstname}} {{lastname}}",
    "html": "<h1>Hello {{firstname}}!</h1><p>Sie sind {{Alter}} Jahre alt.</p>"
  }
}

Technologie

  • Backend: Node.js, Express, TypeScript
  • Datenbank: PostgreSQL mit Prisma ORM
  • Queue: AWS SQS (oder lokale DB-Queue für Development)
  • Email Service: AWS SES
  • Architektur: Microservices (API-Service + Worker-Service)

Playground

Wird automatisch aus deiner Session geladen
preview - Generiert Preview-URL (kein Versand, kein Job)
draft - Speichert Kampagne ohne Versand
test - Sendet nur an Test-Empfänger (erster Recipient aus recipients.inline oder options.test_recipients)
send - Sendet an alle Empfänger
Die letzten Job-IDs werden automatisch geladen

API Dokumentation

Authentifizierung: Alle Requests benötigen Header X-Api-Key

POST /v1/campaigns

Erstellt und versendet Newsletter-Kampagnen

Actions:

  • preview - Generiert eine Vorschau-URL ohne Versand. Keine E-Mails werden gesendet, kein Job wird erstellt. Ideal zum Testen des Designs und der Platzhalter-Ersetzung.
  • draft - Speichert die Kampagne in der Datenbank ohne Versand. Keine E-Mails werden gesendet, kein Job wird erstellt. Nützlich zum Speichern von Entwürfen für späteren Versand.
  • test - Test-Versand: Sendet nur an den ersten Recipient aus recipients.inline (oder an options.test_recipients, falls vorhanden). Erstellt einen Job und sendet tatsächlich E-Mails. Ideal zum Testen des Versand-Prozesses mit echten E-Mails.
  • send - Versendet an alle Recipients in recipients.inline. Erstellt einen Job und sendet E-Mails an alle Empfänger. Dies ist der normale Produktions-Versand.

Unterschiede im Detail:

  • preview vs. draft: preview gibt eine sofortige Vorschau-URL zurück, während draft die Kampagne nur speichert. Beide senden keine E-Mails.
  • test vs. send: test sendet nur an einen Empfänger (den ersten), während send an alle Empfänger sendet. Beide erstellen Jobs und senden echte E-Mails.
  • Job-Status: Nur test und send erstellen Jobs, die über GET /v1/jobs/{jobId} abgefragt werden können. preview und draft geben keine job_id zurück.
POST /v1/campaigns
X-Api-Key: your-api-key

{
  "action": "send",
  "sender": { "from_localpart": "newsletter", "from_name": "Team" },
  "recipients": {
    "inline": [{
      "email": "user@example.com",
      "firstname": "John",
      "lastname": "Doe",
      "attributes": { "Alter": "25" }
    }]
  },
  "content": {
    "subject": "Hello {{firstname}} {{lastname}}",
    "html": "<h1>Hello {{firstname}}!</h1><p>Sie sind {{Alter}} Jahre alt.</p>"
  }
}

GET /v1/preview/{campaignId}

Zeigt HTML-Vorschau der Kampagne im Browser (kein API-Key nötig)

GET /v1/jobs/{jobId}

Gibt Status, Fortschritt und Statistiken des Versand-Jobs zurück

GET /v1/jobs/{jobId}
X-Api-Key: your-api-key

Response: {
  "status": "completed",
  "counts": { "intended": 10, "sent": 10, "failed": 0 }
}

Changelog

Alle wichtigen Änderungen und Updates an MailRudi. Das Changelog ist auch Teil des Dokumentations-Downloads.

Changelog wird geladen...

LLM Integration mit Tool Schema

MailRudi kann direkt aus einem LLM (z.B. OpenAI) per Tool aufgerufen werden. Ein LLM kann Newsletter-Kampagnen erstellen und versenden, ohne dass manuelle API-Calls nötig sind.

Beispiel: TextRudi.com

TextRudi ist ein Social Media Agent für einen Sportverein, der über MailRudi seinen Mitgliedern Newsletter schreiben kann.

Wie funktioniert es?

  1. Der Verein gibt TextRudi eine Anweisung: "Schreibe einen Newsletter über das nächste Turnier"
  2. TextRudi (ein LLM) formuliert den Newsletter-Inhalt
  3. TextRudi ruft das Tool send_club_newsletter auf
  4. Das Tool sendet die Kampagne über die MailRudi API
  5. Die Mitglieder erhalten den Newsletter per E-Mail

Tool-Definition

Das Tool send_club_newsletter ermöglicht es dem LLM, Newsletter-Kampagnen direkt zu erstellen und zu versenden:

{
  "name": "send_club_newsletter",
  "description": "Erstellt und versendet eine Newsletter-Kampagne über POST /v1/campaigns.",
  "parameters": {
    "type": "object",
    "properties": {
      "action": {
        "type": "string",
        "enum": ["preview", "draft", "test", "send"],
        "description": "Aktion für die Kampagne"
      },
      "sender": {
        "type": "object",
        "properties": {
          "from_localpart": { "type": "string" },
          "from_name": { "type": "string" },
          "reply_to": { "type": "string", "format": "email" }
        },
        "required": ["from_localpart"]
      },
      "recipients": {
        "type": "object",
        "properties": {
          "inline": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "email": { "type": "string", "format": "email" },
                "firstname": { "type": "string" },
                "lastname": { "type": "string" },
                "attributes": {
                  "type": "object",
                  "additionalProperties": true
                }
              },
              "required": ["email"],
              "additionalProperties": true
            }
          }
        }
      },
      "content": {
        "type": "object",
        "properties": {
          "subject": { "type": "string" },
          "html": { "type": "string" },
          "text": { "type": "string" }
        },
        "required": ["subject"]
      },
      "style": {
        "type": "object",
        "properties": {
          "brand_color": { "type": "string" },
          "logo_url": { "type": "string", "format": "uri" }
        }
      },
      "options": {
        "type": "object",
        "properties": {
          "test_recipients": {
            "type": "array",
            "items": { "type": "string", "format": "email" }
          }
        }
      }
    },
    "required": ["action", "sender", "content"]
  }
}

Beispiel: TextRudi erstellt einen Newsletter

User zu TextRudi: "Schreibe einen Newsletter über das nächste Fußballturnier am 15. März. Sende ihn an alle Mitglieder."

TextRudi formuliert den Newsletter und ruft das Tool auf:

{
  "action": "send",
  "sender": {
    "from_localpart": "newsletter",
    "from_name": "SV Welschbillig"
  },
  "recipients": {
    "inline": [
      {
        "email": "max.mustermann@example.com",
        "firstname": "Max",
        "lastname": "Mustermann",
        "attributes": {
          "Abteilung": "Fußball",
          "Mitglied_seit": "2020"
        }
      },
      {
        "email": "anna.schmidt@example.com",
        "firstname": "Anna",
        "lastname": "Schmidt",
        "attributes": {
          "Abteilung": "Fußball",
          "Mitglied_seit": "2021"
        }
      }
    ]
  },
  "content": {
    "subject": "Fußballturnier am 15. März – Jetzt anmelden!",
    "html": "<h1>Hallo {{firstname}}!</h1><p>Wir freuen uns, dich über unser nächstes Fußballturnier am 15. März zu informieren.</p><p>Als Mitglied der Abteilung {{Abteilung}} seit {{Mitglied_seit}} möchten wir dich besonders herzlich einladen.</p><p>Melde dich jetzt an!</p>"
  },
  "style": {
    "brand_color": "#0066cc",
    "logo_url": "https://verein.example.com/logo.png"
  }
}

System-Prompt für TextRudi

Damit TextRudi richtig funktioniert, benötigt er einen System-Prompt:

Du bist TextRudi, der Social Media Agent des Sportvereins SV Welschbillig. 
Deine Aufgabe ist es, Newsletter-Inhalte zu formulieren und Versandaktionen 
über das Tool "send_club_newsletter" vorzubereiten.

Wichtige Regeln:

1. Du erzeugst NIE eigene Empfänger. Du verwendest Empfängerdaten genau so, 
   wie sie vom System oder User bereitgestellt wurden.

2. Betreff und HTML-Inhalt formulierst du klar, präzise, geeignet für 
   Vereinskommunikation.

3. Du erzeugst IMMER sauberes HTML (h1, p, ul/li, strong, em).

4. Platzhalter (Placeholders):
   - Verwende {{platzhalter}} Syntax für dynamische Inhalte.
   - Standard-Platzhalter: {{firstname}}, {{lastname}}, {{name}}
   - Custom-Platzhalter: Alle Felder aus "attributes" (z.B. {{Abteilung}}, {{Mitglied_seit}})

5. Versand-Logik:
   - Wenn User nichts sagt → Standard = "preview".
   - Wenn User explizit sagt „entwurf", „speichern" → action = "draft".
   - Wenn User „bitte testen" → action = "test".
   - Wenn User „raus damit", „an alle senden", „jetzt senden" → action = "send".

6. Nach finaler Formulierung rufst du GENAU EIN Tool auf: send_club_newsletter.

7. Antworte immer in deutscher Alltagssprache (du-Form), klar und direkt.

Dein Toolcall ist immer die letzte Ausgabe deiner Antwort.

Backend-Integration

Das Backend von TextRudi muss:

  1. Tool-Call vom LLM empfangen
  2. JSON-Validierung durchführen
  3. HTTP-Request an POST /v1/campaigns senden
  4. Response an das LLM zurückgeben

Beispiel-Implementierung (Node.js):

async function handleToolCall(toolCall) {
  const { action, sender, recipients, content, style, options } = toolCall;
  
  // Validierung
  if (action === 'send' && (!recipients?.inline || recipients.inline.length === 0)) {
    throw new Error('Bei action="send" ist mindestens ein inline recipient erforderlich');
  }
  
  if (!content.html && !content.source) {
    throw new Error('Entweder content.html oder content.source muss vorhanden sein');
  }
  
  // API-Call
  const response = await fetch('https://mailrudi.example.com/v1/campaigns', {
    method: 'POST',
    headers: {
      'X-Api-Key': process.env.API_MAIL_API_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      action,
      sender,
      recipients,
      content,
      style,
      options
    })
  });
  
  if (!response.ok) {
    const errorData = await response.json().catch(() => ({ error: 'Unknown error' }));
    throw new Error(`API Error: ${errorData.message || errorData.error}`);
  }
  
  return await response.json();
}

Platzhalter (Placeholders)

TextRudi kann dynamische Platzhalter verwenden, die für jeden Empfänger individuell ersetzt werden:

  • Standard-Platzhalter: {{firstname}}, {{lastname}}, {{name}}
  • Custom-Platzhalter: Alle Felder aus attributes (z.B. {{Abteilung}}, {{Mitglied_seit}})

Beispiel:

{
  "content": {
    "subject": "Hallo {{firstname}} {{lastname}}",
    "html": "<h1>Hallo {{firstname}}!</h1><p>Du bist Mitglied der Abteilung {{Abteilung}} seit {{Mitglied_seit}}.</p>"
  }
}

→ Für Max Mustermann wird {{firstname}} zu "Max", {{Abteilung}} zu "Fußball", etc.

Fehlerbehandlung

Das Backend sollte API-Fehler in benutzerfreundliche Nachrichten für das LLM umwandeln:

  • 400 Bad Request: Validierungsfehler – spezifische Fehlermeldung mit Feld-Pfad
  • 401 Unauthorized: API-Key ungültig oder fehlt
  • 500 Internal Server Error: Server-Fehler – Bitte später erneut versuchen

Vorteile dieser Integration

  • Automatisierung: Newsletter können per natürlicher Sprache erstellt werden
  • Personalisierung: LLM kann Inhalte basierend auf Empfängerdaten anpassen
  • Einfache Bedienung: Keine technischen API-Kenntnisse nötig
  • Flexibilität: LLM kann verschiedene Newsletter-Formate und -Stile generieren
  • Skalierbarkeit: MailRudi übernimmt den Versand, LLM nur die Inhaltserstellung

Konto-Einstellungen