E-posta sağlayıcısını değiştir

PUT /v1/email-provider · izin email-provider:write · tüm planlar

Ortamın e-postayı nasıl gönderdiğini değiştirir. Beş mod belirtebilirsiniz:

If-Match zorunludur ve okuduğunuz sürümü taşır (get işleminden gelen ETag). Hiç yapılandırılmamış bir ortam için If-Match: "0" gönderin; * bu değerle eşleşmez. Eşleşme olmazsa 412 döner.

Kimlik bilgisi yalnızca yazılabilir. credentials alanında gönderin; hiçbir yanıt bunu geri döndürmez. Bu alanı atlarsanız, kayıtlı kimlik bilgisi yalnızca mod ve hedef (SMTP sunucusu, bağlantı noktası ve kullanıcı adı ya da sağlayıcı) değişmediğinde korunur. Aksi halde istek /credentials required hatasıyla başarısız olur.

Bir değişiklik, son bağlantı testi sonucunu temizler ve yalnızca mod, sağlayıcı ve gönderen alan adı kimliğini içeren bir denetim kaydı (email_provider.updated) yazar; adres ya da kimlik bilgisi asla yazılmaz. Değişiklikten sonra bir bağlantı testi çalıştırın.

İstek

Alan Tür Zorunlu Açıklama
mode string evet platform_default, platform_managed_domain, customer_smtp, customer_api veya customer_webhook.
sender_domain_id string platform_managed_domain Bu ortamın verified durumundaki bir gönderen alan adı (sdom_…). Diğer modlarda kullanılamaz.
webhook_endpoint_id string customer_webhook Bu ortamın etkin bir webhook uç noktası (whe_…). Diğer modlarda kullanılamaz.
from_address string platform_default dışındaki tüm modlar noreply@example.com gibi çıplak gönderen adresi. platform_managed_domain modunda alan adı tam olarak gönderen alan adı olmalıdır. platform_default modunda kullanılamaz.
reply_to string hayır Çıplak yanıt adresi.
provider string customer_api ses, sendgrid, postmark, mailgun veya brevo.
smtp_host string customer_smtp En az iki etiketli DNS adı (IP adresi değil).
smtp_port integer customer_smtp 465 veya 587.
smtp_username string customer_smtp SMTP AUTH kullanıcı adı, 1 ila 256 karakter.
credentials string yukarıya bakın Yalnızca yazılabilir, 1 ila 1024 bayt. customer_smtp: SMTP parolası. customer_api: ses için <region>:<access key id>:<secret access key>; sendgrid ve mailgun için API anahtarı (sağlayıcının AB uç noktası için önek eu:); postmark için sunucu belirteci; brevo için API anahtarı.

Yanıt

Kayıtlı yapılandırmayı içeren 200 OK (şekli get ile aynı) ve yeni sürüme ayarlanmış ETag.

Hatalar

HTTP kod Ne zaman
400 unknown_field İstek gövdesinde bilinmeyen bir alan var ya da JSON nesnesi değil.
412 precondition_failed If-Match, kayıtlı sürüm değil (arada biri kaydetti) ya da ilk yazmadan önce *.
413 payload_too_large İstek gövdesi 16 KiB'den büyük.
428 precondition_required If-Match eksik.
422 validation_failed Bir alan doğrulamayı geçemedi; errors[] alanı ve kuralı belirtir (aşağıya bakın).
401 unauthenticated Kimlik bilgisi eksik, geçersiz ya da iptal edilmiş.
403 insufficient_permissions Kimlik bilgisinin gerekli izni yok.
429 rate_limited Hız sınırı aşıldı; Retry-After süresinden sonra yeniden deneyin.
503 service_unavailable Bir bağımlılık geçici olarak kullanılamıyor; Retry-After süresinden sonra yeniden deneyin.

errors[] içindeki kurallar:

İşaretçi Kural Anlamı
/mode required, invalid Mod eksik ya da bilinmiyor.
/mode delivery_mode_unavailable Mod bu dağıtımda gönderemez: customer_webhook için webhook teslim bağlantısının etkin olması, platform_managed_domain için DKIM imzalamanın etkin olması, müşteri modları için ise giden teslimin etkin olması gerekir.
/mode production_only platform_managed_domain yalnızca üretim ortamlarında kullanılabilir.
/mode fields_not_allowed Modun kabul etmediği bir alan gönderildi. customer_webhook, provider, SMTP alanlarını, credentials veya sender_domain_id alanını kabul etmez.
/sender_domain_id required, invalid platform_managed_domain gönderen alan adı kimliği olmadan gönderildi ya da kimlik bir sdom_… kimliği değil.
/sender_domain_id not_found Bu ortamda bu kimliğe sahip bir gönderen alan adı yok. Başka bir ortamın alan adı da aynı yanıtı alır.
/sender_domain_id not_verified Gönderen alan adı var ama verified değil (DNS kayıtları bekliyor, bozulmuş ya da devre dışı).
/from_address not_in_sender_domain Gönderen adresi tam olarak gönderen alan adına ait değil.
/webhook_endpoint_id required, invalid customer_webhook uç nokta kimliği olmadan gönderildi ya da kimlik bir whe_… kimliği değil.
/webhook_endpoint_id not_found Bu ortamda bu kimliğe sahip bir webhook uç noktası yok. Başka bir ortamın uç noktası da aynı yanıtı alır.
/webhook_endpoint_id not_enabled Uç nokta var ama etkin değil.
/credentials required Mod ya da hedef değişti ve yeni kimlik bilgisi gönderilmedi.
/credentials invalid Kimlik bilgisi boş, çok uzun, denetim karakterleri içeriyor ya da sağlayıcının biçimine uymuyor.
/from_address, /reply_to, /provider, /smtp_host, /smtp_port, /smtp_username required, invalid Alan eksik ya da modda geçersiz.

Webhook teslimi

customer_webhook modunda KeenAuth iletiyi göndermez ve hiçbir zaman kendi gönderimine geri dönmez. Oluşturduğu her ileti (konu, HTML ve metin) için uç noktaya HTTPS üzerinden imzalı bir olay gönderir:

Gövde (schema_version 1):

{
  "id": "msg_01k7xq3m5r8t9v0w1x2y3z4a5b",
  "type": "email.send_requested",
  "schema_version": 1,
  "environment_id": "env_01k7xq3m5r8t9v0w1x2y3z4a5b",
  "occurred_at": "2026-10-27T09:00:00Z",
  "data": {
    "message_id": "msg_01k7xq3m5r8t9v0w1x2y3z4a5b",
    "family": "email_otp",
    "locale": "en",
    "to": "ada@example.com",
    "from": "auth@example.com",
    "from_name": "Example",
    "reply_to": "support@example.com",
    "subject": "Your sign-in code",
    "html": "<p>Your code is 123456</p>",
    "text": "Your code is 123456",
    "link": "https://example.keenauth.app/...",
    "code": "123456"
  }
}
data alanı Açıklama
message_id, family, locale İleti kimliği (zarftaki id ile aynı), şablon ailesi ve iletinin oluşturulduğu dil.
to Alıcı adresi.
from, from_name, reply_to Yalnızca ayarlandığında bulunur: from, bu yapılandırmanın from_address değeridir.
subject Oluşturulmuş konu.
html, text Oluşturulmuş içerik; en az biri bulunur.
link, code Yalnızca iletide oturum açma bağlantısı ya da tek kullanımlık kod varsa bulunur.

İmza, v1, ile birlikte HMAC-SHA256(key, "<webhook-id>.<webhook-timestamp>.<raw body>") değerinin base64 karşılığıdır. Anahtar, uç noktanın imzalama sırrındaki idp_whsec_ öneki ardından gelen 43 base62 karakterinin kodladığı 32 baytlık değerdir. Ham istek gövdesini ayrıştırmadan önce doğrulayın ve saatinizden 300 saniyeden fazla uzak bir zaman damgasını reddedin. Sır döndürme sırasında webhook-signature boşlukla ayrılmış iki imza içerir.

Python:

import base64, hashlib, hmac, time

ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
PREFIX = "idp_whsec_"


def signing_key(secret: str) -> bytes:
    """The HMAC key is the 32-byte value behind the 43 base62 characters after the prefix."""
    body = secret[len(PREFIX):len(PREFIX) + 43]
    n = 0
    for ch in body:
        n = n * 62 + ALPHABET.index(ch)
    return n.to_bytes(32, "big")


def verify(secret: str, headers: dict, raw_body: bytes, tolerance: int = 300) -> bool:
    msg_id = headers["webhook-id"]
    ts = headers["webhook-timestamp"]
    if abs(time.time() - int(ts)) > tolerance:
        return False
    signed = f"{msg_id}.{ts}.".encode() + raw_body
    mac = hmac.new(signing_key(secret), signed, hashlib.sha256).digest()
    expected = "v1," + base64.b64encode(mac).decode()
    # webhook-signature holds one or more space-separated signatures.
    return any(hmac.compare_digest(expected, s) for s in headers["webhook-signature"].split(" "))

Node.js:

import { createHmac, timingSafeEqual } from "node:crypto";

const ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
const PREFIX = "idp_whsec_";

// The HMAC key is the 32-byte value behind the 43 base62 characters after the prefix.
function signingKey(secret) {
  let n = 0n;
  for (const ch of secret.slice(PREFIX.length, PREFIX.length + 43)) n = n * 62n + BigInt(ALPHABET.indexOf(ch));
  return Buffer.from(n.toString(16).padStart(64, "0"), "hex");
}

export function verify(secret, headers, rawBody, tolerance = 300) {
  const id = headers["webhook-id"];
  const ts = headers["webhook-timestamp"];
  if (Math.abs(Date.now() / 1000 - Number(ts)) > tolerance) return false;
  const mac = createHmac("sha256", signingKey(secret)).update(`${id}.${ts}.`).update(rawBody).digest();
  const expected = Buffer.from(`v1,${mac.toString("base64")}`);
  // webhook-signature holds one or more space-separated signatures.
  return headers["webhook-signature"].split(" ").some((s) => {
    const got = Buffer.from(s);
    return got.length === expected.length && timingSafeEqual(got, expected);
  });
}

Örnek

curl -X PUT https://api.eu.keenauth.com/v1/email-provider \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -H 'If-Match: "0"' \
  -d @email-provider.json

email-provider.json:

{
  "mode": "customer_smtp",
  "from_address": "no-reply@example.com",
  "smtp_host": "smtp.example.com",
  "smtp_port": 587,
  "smtp_username": "mailer",
  "credentials": "<SMTP password>"
}

İletileri bir webhook uç noktasına iletmek için:

{
  "mode": "customer_webhook",
  "webhook_endpoint_id": "whe_01k7xq3m5r8t9v0w1x2y3z4a5b",
  "from_address": "auth@example.com"
}

Bu modda test ile bağlantı yoktur; uç noktayı Webhook uç noktasını test et ile test edin.

Doğrulanmış gönderen alan adınızdan göndermek için:

{
  "mode": "platform_managed_domain",
  "sender_domain_id": "sdom_01k7xq3m5r8t9v0w1x2y3z4a5b",
  "from_address": "auth@mail.example.com",
  "reply_to": "support@example.com"
}

Bu modda test ile bağlantı yoktur; bunun yerine alan adını doğrula ile kontrol edin.

CLI: keenauth email-provider put --if-match '"0"' --data @email-provider.json. Kimlik bilgisini bir dosyada tutun ya da standart girdi üzerinden verin (--data -), böylece komut geçmişinize yazılmasın.