<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>mailcow - NET73</title>
	<atom:link href="https://net73.de/tag/mailcow/feed/" rel="self" type="application/rss+xml" />
	<link>https://net73.de</link>
	<description>Techblog, Freizeitblog &#38; Webhosting</description>
	<lastBuildDate>Sat, 15 Nov 2025 22:19:26 +0000</lastBuildDate>
	<language>de</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://net73.de/wp-content/uploads/2025/11/favicon512-150x150.png</url>
	<title>mailcow - NET73</title>
	<link>https://net73.de</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Automatisches DNS-Setup mit Cloudflare- und Mailcow-API</title>
		<link>https://net73.de/automatisches-dns-setup-mit-cloudflare-und-mailcow-api/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=automatisches-dns-setup-mit-cloudflare-und-mailcow-api</link>
					<comments>https://net73.de/automatisches-dns-setup-mit-cloudflare-und-mailcow-api/#respond</comments>
		
		<dc:creator><![CDATA[Marc Eggert]]></dc:creator>
		<pubDate>Sat, 15 Nov 2025 22:19:25 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[WebDev]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[mail]]></category>
		<category><![CDATA[mailcow]]></category>
		<category><![CDATA[postfix]]></category>
		<guid isPermaLink="false">https://net73.de/?p=3685</guid>

					<description><![CDATA[<p>Wenn du mehrere Domains mit Mailcow betreibst, wiederholt sich immer derselbe Nervkram: MX setzen, SPF schreiben, DKIM eintragen, DMARC hinterher, Autoconfig und Co. nicht vergessen. Weil ich darauf keine Lust mehr hatte, erledigt das bei mir jetzt ein Bash-Script, das: In diesem Beitrag zeige ich dir: Am Ende findest du das komplette Script und die [&#8230;]</p>
<p>The post <a href="https://net73.de/automatisches-dns-setup-mit-cloudflare-und-mailcow-api/">Automatisches DNS-Setup mit Cloudflare- und Mailcow-API</a> first appeared on <a href="https://net73.de">NET73</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Wenn du mehrere Domains mit Mailcow betreibst, wiederholt sich immer derselbe Nervkram: MX setzen, SPF schreiben, DKIM eintragen, DMARC hinterher, Autoconfig und Co. nicht vergessen.</p>



<p>Weil ich darauf keine Lust mehr hatte, erledigt das bei mir jetzt ein Bash-Script, das:</p>



<ul class="wp-block-list">
<li>die DNS-Zone bei Cloudflare findet</li>



<li>den DKIM-Key automatisch aus der Mailcow-API zieht</li>



<li>alle relevanten DNS-Records für Mailcow setzt bzw. aktualisiert</li>
</ul>



<p></p>



<p>In diesem Beitrag zeige ich dir:</p>



<ul class="wp-block-list">
<li>welche DNS-Records angelegt werden und warum</li>



<li>wie das Script aufgebaut ist</li>



<li>wie du die <code>.env</code>-Datei für deine Secrets nutzt</li>



<li>wie du das Ganze in der Praxis einsetzt</li>
</ul>



<p>Am Ende findest du das komplette Script und die passende <code>.env</code>-Datei als kopierbare Code-Blöcke.</p>



<h2 class="wp-block-heading">Was das Script macht – einmal in Kurzform</h2>



<p>Für eine Domain wie <code>example.org</code> macht das Script:</p>



<ul class="wp-block-list">
<li><strong>MX</strong>: zeigt auf deinen Mailcow-Host (z. B. <code>mail.example.org</code>)</li>



<li><strong>SPF (TXT)</strong>: Standardmäßig <code>v=spf1 mx -all</code></li>



<li><strong>DKIM (TXT)</strong>: Holt den Key aus der Mailcow-API und legt <code>dkim._domainkey.example.org</code> (oder deinen Selector) an</li>



<li><strong>DMARC (TXT)</strong>: einfacher Default <code>v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@example.org</code></li>



<li><strong>Autoconfig/Autodiscover (CNAME)</strong>: <code>autoconfig.example.org</code> und <code>autodiscover.example.org</code> → <code>mail.example.org</code></li>



<li><strong>MTA-STS (CNAME + TXT)</strong>: <code>mta-sts.example.org</code> + <code>_mta-sts.example.org</code></li>



<li><strong>TLS-Reporting (TXT)</strong>: <code>_smtp._tls.example.org</code> für TLS-Reports</li>
</ul>



<p>Wichtig: Existierende DKIM-Records mit anderen Selektoren (z. B. vom alten Mailserver) bleiben absichtlich unberührt. Solange du noch über den alten Server versendest oder alte Mails nachsigniert werden, ist das hilfreich. Aufräumen kannst du später manuell, wenn der alte Server wirklich komplett raus ist.</p>



<h2 class="wp-block-heading">Voraussetzungen</h2>



<p>Damit das Script sauber läuft, brauchst du:</p>



<ul class="wp-block-list">
<li>macOS oder Linux mit:</li>



<li><code>bash</code></li>



<li><code>curl</code></li>



<li><code>jq</code></li>



<li><strong>Cloudflare-API-Token</strong> mit:
<ul class="wp-block-list">
<li>Zone → DNS → Edit</li>



<li>Zone → Zone → Read</li>
</ul>
</li>



<li><strong>Mailcow-API-Key</strong> mit Leserechten für DKIM (<code>/api/v1/get/dkim/{domain}</code>)</li>



<li>Eine <strong>Mailcow-Instanz</strong>, z. B. <code>https://mailcow.example.org</code></li>



<li>Deine Domains müssen bereits als Zonen bei Cloudflare existieren</li>
</ul>



<h2 class="wp-block-heading">Wie das Script funktioniert</h2>



<h3 class="wp-block-heading">1. <code>.env</code>-Datei laden</h3>



<p>Das Script liest – falls vorhanden – eine <code>.env</code> im selben Verzeichnis.<br>Darin stehen die sensitiven Daten wie API-Tokens und URLs. So bleiben die Werte getrennt vom Script selbst.</p>



<pre class="wp-block-code"><code lang="bash" class="language-bash line-numbers"># .env automatisch laden, falls vorhanden
if [ -f .env ]; then
  echo "Lade .env..."
  set -o allexport
  source .env
  set +o allexport
fi</code></pre>



<p>Die Kombination aus <code>set -o allexport</code> und <code>source .env</code> sorgt dafür, dass jede Variable aus der <code>.env</code> automatisch als Umgebungsvariable exportiert wird, ohne dass du überall <code>export</code> schreiben musst.</p>



<h3 class="wp-block-heading">2. API-Variablen &amp; Standardwerte</h3>



<p>Das Script nutzt:</p>



<ul class="wp-block-list">
<li>Cloudflare API Token (<code>CF_API_TOKEN</code>)</li>



<li>Mailcow API Key (<code>MAILCOW_API_KEY</code>)</li>



<li>Mailcow Base-URL (<code>MAILCOW_BASEURL</code>)</li>



<li>Mail-Host / MX-Ziel (<code>MAIL_HOST</code>)</li>
</ul>



<p>Dazu kommen ein paar Default-Policies:</p>



<pre class="wp-block-code"><code lang="bash" class="language-bash line-numbers">CF_API_TOKEN="${CF_API_TOKEN:-}"
CF_API_BASE="https://api.cloudflare.com/client/v4"

MAILCOW_BASEURL="${MAILCOW_BASEURL:-https://mail.net73.de}"
MAILCOW_API_KEY="${MAILCOW_API_KEY:-}"

DOMAIN="${1:-}"                      # Domain als CLI-Argument
MAIL_HOST="${MAIL_HOST:-mail.net73.de}"

SPF_VALUE="${SPF_VALUE:-v=spf1 mx -all}"
DMARC_VALUE="${DMARC_VALUE:-v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@${DOMAIN}}"
MTA_STS_ID="${MTA_STS_ID:-$(date +%Y%m%d)}"

TTL=3600</code></pre>



<p>Ein paar Punkte dazu:</p>



<ul class="wp-block-list">
<li><code>DOMAIN</code> kommt als erstes Argument, z. B. <code>./setup_mail_dns.sh example.org</code></li>



<li><code>SPF_VALUE</code>, <code>DMARC_VALUE</code>, <code>MTA_STS_ID</code> kannst du bei Bedarf über die <code>.env</code> überschreiben</li>



<li><code>MTA_STS_ID</code> nutzt einfach das aktuelle Datum als ID – reicht völlig</li>
</ul>



<h3 class="wp-block-heading">3. Fehler-Handling &amp; Pflicht-Variablen</h3>



<p>Damit du nicht mit halbgaren Umgebungen arbeitest, prüft das Script die nötigsten Variablen:</p>



<pre class="wp-block-code"><code lang="bash" class="language-bash line-numbers">err() { echo "ERROR: $*" &gt;&amp;2; exit 1; }

require_env() {
  local name="$1"
  [ -n "${!name:-}" ] || err "Umgebungsvariable $name ist nicht gesetzt."
}

[ -n "$DOMAIN" ] || err "Usage: $0 &lt;domain.tld&gt; (z.B. example.org)"

require_env CF_API_TOKEN
require_env MAILCOW_API_KEY
require_env MAILCOW_BASEURL</code></pre>



<p>Wenn z. B. <code>CF_API_TOKEN</code> fehlt, bricht das Script mit einer sauberen Fehlermeldung ab.</p>



<h3 class="wp-block-heading">4. Cloudflare-Zone finden</h3>



<p>Cloudflare arbeitet intern mit <strong>Zone-IDs</strong>. Die holt sich das Script über die API:</p>



<pre class="wp-block-code"><code lang="bash" class="language-bash line-numbers">echo "Suche Cloudflare Zone-ID für ${DOMAIN}..."
ZONE_ID=$(
  curl -s -X GET "${CF_API_BASE}/zones?name=${DOMAIN}" \
       -H "Authorization: Bearer ${CF_API_TOKEN}" \
       -H "Content-Type: application/json" \
    | jq -r '.result[0].id // empty'
)

[ -n "$ZONE_ID" ] || err "Konnte Zone-ID für ${DOMAIN} nicht finden. Stimmt die Domain in Cloudflare?"</code></pre>



<p>Damit bist du unabhängig von irgendwelchen manuellen IDs.</p>



<h3 class="wp-block-heading">5. DKIM-Key aus der Mailcow-API ziehen</h3>



<p>Statt den DKIM-Key per Copy &amp; Paste ins DNS zu kippen, zieht das Script den aktuellen Key direkt aus Mailcow:</p>



<pre class="wp-block-code"><code lang="bash" class="language-bash line-numbers">echo "Hole DKIM-Key von Mailcow für ${DOMAIN}..."

DKIM_JSON=$(
  curl -s -X GET "${MAILCOW_BASEURL}/api/v1/get/dkim/${DOMAIN}" \
       -H "X-API-Key: ${MAILCOW_API_KEY}"
)

DKIM_SELECTOR=$(echo "$DKIM_JSON" | jq -r '.dkim_selector // "dkim"')
DKIM_TXT=$(echo "$DKIM_JSON" | jq -r '.dkim_txt // empty')

[ -n "$DKIM_TXT" ] || err "Kein DKIM-TXT von Mailcow erhalten. Prüfe API-Key, Domain oder ob ein DKIM-Key angelegt wurde."

DKIM_NAME="${DKIM_SELECTOR}._domainkey.${DOMAIN}"

echo "DKIM-Selector: ${DKIM_SELECTOR}"
echo "DKIM-Record:   ${DKIM_NAME}"</code></pre>



<ul class="wp-block-list">
<li><code>dkim_selector</code>: z. B. <code>dkim</code></li>



<li><code>dkim_txt</code>: der komplette DKIM-Record, wie er ins DNS gehört (inkl. <code>v=DKIM1; k=rsa; p=…</code>)</li>
</ul>



<p>Das Script macht daraus später einen TXT-Record bei Cloudflare.</p>



<h3 class="wp-block-heading">6. DNS-Record-Handling mit <code>cf_upsert_record</code></h3>



<p>Der eigentliche Trick steckt in einer kleinen Hilfsfunktion, die Records entweder <strong>aktualisiert</strong> oder <strong>neu anlegt</strong>:</p>



<pre class="wp-block-code"><code lang="bash" class="language-bash line-numbers">cf_upsert_record() {
  local TYPE="$1"   # A, CNAME, MX, TXT, ...
  local NAME="$2"
  local CONTENT="$3"
  local TTL_VAL="$4"
  local PROXIED="${5:-false}"   # true/false (nur für A/AAAA/CNAME)
  local PRIORITY="${6:-0}"      # nur für MX relevant

  local DATA
  if [[ "$TYPE" == "MX" ]]; then
    DATA=$(jq -n --arg type "$TYPE" --arg name "$NAME" --arg content "$CONTENT" \
               --argjson ttl "$TTL_VAL" --argjson priority "$PRIORITY" \
               '{type:$type,name:$name,content:$content,ttl:$ttl,priority:$priority}')
  elif [[ "$TYPE" == "TXT" ]]; then
    DATA=$(jq -n --arg type "$TYPE" --arg name "$NAME" --arg content "$CONTENT" \
               --argjson ttl "$TTL_VAL" \
               '{type:$type,name:$name,content:$content,ttl:$ttl}')
  else
    DATA=$(jq -n --arg type "$TYPE" --arg name "$NAME" --arg content "$CONTENT" \
               --argjson ttl "$TTL_VAL" --argjson proxied "$PROXIED" \
               '{type:$type,name:$name,content:$content,ttl:$ttl,proxied:$proxied}')
  fi

  local EXISTING_ID
  EXISTING_ID=$(
    curl -s -X GET "${CF_API_BASE}/zones/${ZONE_ID}/dns_records?type=${TYPE}&amp;name=${NAME}" \
         -H "Authorization: Bearer ${CF_API_TOKEN}" \
         -H "Content-Type: application/json" \
      | jq -r '.result[0].id // empty'
  )

  if [[ -n "$EXISTING_ID" ]]; then
    curl -s -X PUT "${CF_API_BASE}/zones/${ZONE_ID}/dns_records/${EXISTING_ID}" \
         -H "Authorization: Bearer ${CF_API_TOKEN}" \
         -H "Content-Type: application/json" \
         --data "${DATA}" &gt;/dev/null
    echo "Updated ${TYPE} ${NAME}"
  else
    curl -s -X POST "${CF_API_BASE}/zones/${ZONE_ID}/dns_records" \
         -H "Authorization: Bearer ${CF_API_TOKEN}" \
         -H "Content-Type: application/json" \
         --data "${DATA}" &gt;/dev/null
    echo "Created ${TYPE} ${NAME}"
  fi
}</code></pre>



<p>Damit musst du dich im Hauptteil des Scripts nicht mehr darum kümmern, ob ein Record schon existiert.</p>



<h3 class="wp-block-heading">7. Welche DNS-Records das Script setzt</h3>



<p>Im letzten Block legt das Script alle relevanten Records für die Domain an bzw. aktualisiert sie:</p>



<pre class="wp-block-code"><code lang="bash" class="language-bash line-numbers">echo "Lege Standard-Mailcow-DNS-Einträge für ${DOMAIN} an / aktualisiere sie..."

# MX
cf_upsert_record "MX" "${DOMAIN}" "${MAIL_HOST}" "${TTL}" "false" 10

# SPF
cf_upsert_record "TXT" "${DOMAIN}" "${SPF_VALUE}" "${TTL}"

# DKIM
cf_upsert_record "TXT" "${DKIM_NAME}" "${DKIM_TXT}" "${TTL}"

# DMARC
cf_upsert_record "TXT" "_dmarc.${DOMAIN}" "${DMARC_VALUE}" "${TTL}"

# Autoconfig / Autodiscover
cf_upsert_record "CNAME" "autoconfig.${DOMAIN}" "${MAIL_HOST}" "${TTL}" "false"
cf_upsert_record "CNAME" "autodiscover.${DOMAIN}" "${MAIL_HOST}" "${TTL}" "false"

# Optional: MTA-STS &amp; TLS-RPT
cf_upsert_record "CNAME" "mta-sts.${DOMAIN}" "${MAIL_HOST}" "${TTL}" "false"
cf_upsert_record "TXT" "_mta-sts.${DOMAIN}" "v=STSv1; id=${MTA_STS_ID}" "${TTL}"
cf_upsert_record "TXT" "_smtp._tls.${DOMAIN}" "v=TLSRPTv1; rua=mailto:tls-reports@${DOMAIN}" "${TTL}"

echo "Fertig. Bitte DNS-Änderungen und Mailcow-Check-Tools (z.B. Mailcow DNS-Check, mail-tester.com) prüfen."</code></pre>



<p>Kurz dazu:</p>



<ul class="wp-block-list">
<li><strong>MX</strong>: zeigt auf deinen Mailcow-Host, Proxy ist ausgeschaltet (Cloudflare darf kein SMTP sprechen)</li>



<li><strong>SPF</strong>: minimalistisch, erlaubt nur den MX-Server</li>



<li><strong>DKIM</strong>: nutzt den aktuellen Key aus Mailcow</li>



<li><strong>DMARC</strong>: schickt Reports an <code>dmarc-reports@deine-domain</code></li>



<li><strong>Autoconfig/Autodiscover</strong>: hilft Mail-Clients bei der Auto-Konfiguration</li>



<li><strong>MTA-STS &amp; TLSRPT</strong>: härten Transportverschlüsselung und sammeln TLS-Reports</li>
</ul>



<h3 class="wp-block-heading">8. Was ist mit alten DKIM-Records?</h3>



<p>Viele migrieren von einem alten Mailserver (anderer Provider, eigene Kiste, …) zu Mailcow. Oft existieren dort bereits DKIM-Records mit einem anderen Selector, z. B.:</p>



<ul class="wp-block-list">
<li><code>google._domainkey.example.org</code></li>



<li><code>mailjet._domainkey.example.org</code></li>



<li><code>oldserver1._domainkey.example.org</code></li>
</ul>



<p>Das Script fasst diese <strong>bewusst nicht an</strong>.</p>



<p>Das ist kein Bug, sondern Feature: Solange der alte Mailserver noch Mails mit seinem Key signiert (oder alte Nachrichten Relevanz haben), ist es sinnvoll, die alten DKIM-Keys im DNS zu lassen. Erst wenn du sicher bist, dass der alte Server endgültig außer Betrieb ist, kannst du seine Selektoren gezielt löschen – das ist eine Entscheidung, die ich lieber manuell treffen möchte.</p>



<h2 class="wp-block-heading">Praxis: Setup, .env und Aufruf</h2>



<h3 class="wp-block-heading">1. Script speichern</h3>



<p>Speichere das Script z. B. als <code>setup_mail_dns.sh</code> in ein Verzeichnis deiner Wahl.</p>



<h3 class="wp-block-heading">2. <code>.env</code> anlegen</h3>



<p>Im selben Verzeichnis legst du eine Datei <code>.env</code> an, z. B.:</p>



<pre class="wp-block-code"><code lang="bash" class="language-bash line-numbers">export CF_API_TOKEN="xxxxxxx"
export MAILCOW_API_KEY="xxxxxxx"
export MAILCOW_BASEURL="https://mailcow.example.org"
export MAIL_HOST="mail.example.org"</code></pre>



<p>Für weitere Domains kannst du <code>MAIL_HOST</code> gleich lassen, wenn alle denselben Mailcow-Host nutzen.</p>



<h3 class="wp-block-heading">3. Script ausführbar machen und aufrufen</h3>



<pre class="wp-block-code"><code lang="bash" class="language-bash line-numbers">chmod +x setup_mail_dns.sh
./setup_mail_dns.sh example.org</code></pre>



<p>Für jede weitere Domain:</p>



<pre class="wp-block-code"><code lang="bash" class="language-bash line-numbers">./setup_mail_dns.sh andere-domain.tld</code></pre>



<h2 class="wp-block-heading">Komplettes Script (kopierfertig)</h2>



<pre class="wp-block-code"><code lang="bash" class="language-bash line-numbers">#!/usr/bin/env bash
# .env automatisch laden, falls vorhanden
if [ -f .env ]; then
  echo "Lade .env..."
  set -o allexport
  source .env
  set +o allexport
fi

set -euo pipefail

# 1) Cloudflare
CF_API_TOKEN="${CF_API_TOKEN:-}"    # besser als Umgebungsvariable setzen
CF_API_BASE="https://api.cloudflare.com/client/v4"

# 2) Mailcow
MAILCOW_BASEURL="${MAILCOW_BASEURL:-https://mail.net73.de}"  # ohne trailing slash
MAILCOW_API_KEY="${MAILCOW_API_KEY:-}"

# 3) Domain &amp; Mailserver
DOMAIN="${1:-}"           # z.B. example.org (als Argument)
MAIL_HOST="${MAIL_HOST:-mail.net73.de}"  # MX-Ziel

# 4) Mail-Policy Defaults
SPF_VALUE="${SPF_VALUE:-v=spf1 mx -all}"
DMARC_VALUE="${DMARC_VALUE:-v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@${DOMAIN}}"
MTA_STS_ID="${MTA_STS_ID:-$(date +%Y%m%d)}"  # z.B. 20251115

TTL=3600

# ============================
# HILFSFUNKTIONEN
# ============================

err() { echo "ERROR: $*" &gt;&amp;2; exit 1; }

require_env() {
  local name="$1"
  [ -n "${!name:-}" ] || err "Umgebungsvariable $name ist nicht gesetzt."
}

# Upsert-Funktion für DNS-Records
cf_upsert_record() {
  local TYPE="$1"   # A, CNAME, MX, TXT, ...
  local NAME="$2"
  local CONTENT="$3"
  local TTL_VAL="$4"
  local PROXIED="${5:-false}"   # true/false (nur für A/AAAA/CNAME)
  local PRIORITY="${6:-0}"      # nur für MX relevant

  # JSON-Body je nach Typ bauen
  local DATA
  if [[ "$TYPE" == "MX" ]]; then
    DATA=$(jq -n --arg type "$TYPE" --arg name "$NAME" --arg content "$CONTENT" \
               --argjson ttl "$TTL_VAL" --argjson priority "$PRIORITY" \
               '{type:$type,name:$name,content:$content,ttl:$ttl,priority:$priority}')
  elif [[ "$TYPE" == "TXT" ]]; then
    DATA=$(jq -n --arg type "$TYPE" --arg name "$NAME" --arg content "$CONTENT" \
               --argjson ttl "$TTL_VAL" \
               '{type:$type,name:$name,content:$content,ttl:$ttl}')
  else
    # A/AAAA/CNAME mit proxied
    DATA=$(jq -n --arg type "$TYPE" --arg name "$NAME" --arg content "$CONTENT" \
               --argjson ttl "$TTL_VAL" --argjson proxied "$PROXIED" \
               '{type:$type,name:$name,content:$content,ttl:$ttl,proxied:$proxied}')
  fi

  # Existing Record abfragen
  local EXISTING_ID
  EXISTING_ID=$(
    curl -s -X GET "${CF_API_BASE}/zones/${ZONE_ID}/dns_records?type=${TYPE}&amp;name=${NAME}" \
         -H "Authorization: Bearer ${CF_API_TOKEN}" \
         -H "Content-Type: application/json" \
      | jq -r '.result[0].id // empty'
  )

  if [[ -n "$EXISTING_ID" ]]; then
    curl -s -X PUT "${CF_API_BASE}/zones/${ZONE_ID}/dns_records/${EXISTING_ID}" \
         -H "Authorization: Bearer ${CF_API_TOKEN}" \
         -H "Content-Type: application/json" \
         --data "${DATA}" &gt;/dev/null
    echo "Updated ${TYPE} ${NAME}"
  else
    curl -s -X POST "${CF_API_BASE}/zones/${ZONE_ID}/dns_records" \
         -H "Authorization: Bearer ${CF_API_TOKEN}" \
         -H "Content-Type: application/json" \
         --data "${DATA}" &gt;/dev/null
    echo "Created ${TYPE} ${NAME}"
  fi
}

# ============================
# CHECKS
# ============================

[ -n "$DOMAIN" ] || err "Usage: $0 &lt;domain.tld&gt; (z.B. example.org)"

require_env CF_API_TOKEN
require_env MAILCOW_API_KEY
require_env MAILCOW_BASEURL

# ============================
# ZONE ID HOLEN
# ============================

echo "Suche Cloudflare Zone-ID für ${DOMAIN}..."
ZONE_ID=$(
  curl -s -X GET "${CF_API_BASE}/zones?name=${DOMAIN}" \
       -H "Authorization: Bearer ${CF_API_TOKEN}" \
       -H "Content-Type: application/json" \
    | jq -r '.result[0].id // empty'
)

[ -n "$ZONE_ID" ] || err "Konnte Zone-ID für ${DOMAIN} nicht finden. Stimmt die Domain in Cloudflare?"

echo "Gefundene Zone-ID: ${ZONE_ID}"

# ============================
# DKIM PER MAILCOW-API HOLEN
# ============================

echo "Hole DKIM-Key von Mailcow für ${DOMAIN}..."

DKIM_JSON=$(
  curl -s -X GET "${MAILCOW_BASEURL}/api/v1/get/dkim/${DOMAIN}" \
       -H "X-API-Key: ${MAILCOW_API_KEY}"
)

DKIM_SELECTOR=$(echo "$DKIM_JSON" | jq -r '.dkim_selector // "dkim"')
DKIM_TXT=$(echo "$DKIM_JSON" | jq -r '.dkim_txt // empty')

[ -n "$DKIM_TXT" ] || err "Kein DKIM-TXT von Mailcow erhalten. Prüfe API-Key, Domain oder ob ein DKIM-Key angelegt wurde."

DKIM_NAME="${DKIM_SELECTOR}._domainkey.${DOMAIN}"

echo "DKIM-Selector: ${DKIM_SELECTOR}"
echo "DKIM-Record:   ${DKIM_NAME}"

# ============================
# DNS-RECORDS ANLEGEN / AKTUALISIEREN
# ============================

echo "Lege Standard-Mailcow-DNS-Einträge für ${DOMAIN} an / aktualisiere sie..."

# MX
cf_upsert_record "MX" "${DOMAIN}" "${MAIL_HOST}" "${TTL}" "false" 10

# SPF
cf_upsert_record "TXT" "${DOMAIN}" "${SPF_VALUE}" "${TTL}"

# DKIM
cf_upsert_record "TXT" "${DKIM_NAME}" "${DKIM_TXT}" "${TTL}"

# DMARC
cf_upsert_record "TXT" "_dmarc.${DOMAIN}" "${DMARC_VALUE}" "${TTL}"

# Autoconfig / Autodiscover
cf_upsert_record "CNAME" "autoconfig.${DOMAIN}" "${MAIL_HOST}" "${TTL}" "false"
cf_upsert_record "CNAME" "autodiscover.${DOMAIN}" "${MAIL_HOST}" "${TTL}" "false"

# Optional: MTA-STS &amp; TLS-RPT
cf_upsert_record "CNAME" "mta-sts.${DOMAIN}" "${MAIL_HOST}" "${TTL}" "false"
cf_upsert_record "TXT" "_mta-sts.${DOMAIN}" "v=STSv1; id=${MTA_STS_ID}" "${TTL}"
cf_upsert_record "TXT" "_smtp._tls.${DOMAIN}" "v=TLSRPTv1; rua=mailto:tls-reports@${DOMAIN}" "${TTL}"

echo "Fertig. Bitte DNS-Änderungen und Mailcow-Check-Tools (z.B. Mailcow DNS-Check, mail-tester.com) prüfen."</code></pre>



<h2 class="wp-block-heading">Beispiel-.env zum direkten Einsatz</h2>



<pre class="wp-block-code"><code lang="bash" class="language-bash line-numbers">export CF_API_TOKEN="xxxxxxx"
export MAILCOW_API_KEY="xxxxxxx"
export MAILCOW_BASEURL="https://mailcow.example.org"
export MAIL_HOST="mail.example.org"</code></pre><p>The post <a href="https://net73.de/automatisches-dns-setup-mit-cloudflare-und-mailcow-api/">Automatisches DNS-Setup mit Cloudflare- und Mailcow-API</a> first appeared on <a href="https://net73.de">NET73</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://net73.de/automatisches-dns-setup-mit-cloudflare-und-mailcow-api/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
