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:
platform_default: KeenAuth, kendi adresinden gönderir. Yalnızcareply_toayarlanabilir.platform_managed_domain: KeenAuth, kendi alan adınızdan gönderir ve bu alan adı için DKIM ile imzalar. Önce alan adını e-posta gönderen alan adları üzerinden ekleyin ve DNS kayıtlarını yayımlayın; alan adının durumuverifiedolmalıdır.sender_domain_idve tam olarak bu alan adına ait birfrom_addressayarlayın (alt alan adı olmamalıdır). Yalnızca üretim ortamlarında kullanılabilir. Alan adı daha sonra doğrulanmış durumunu kaybederse, alan adı yeniden doğrulanana kadar e-postalarplatform_defaultile gönderilir; yapılandırma kendisi değişmez.customer_smtp:465(örtük TLS) veya587(STARTTLS zorunludur) numaralı bağlantı noktasındaki SMTP sunucunuz. Sertifika, sunucu adı için geçerli olmalıdır.customer_api:ses,sendgrid,postmark,mailgunveyabrevoüzerindeki hesabınız.customer_webhook: KeenAuth iletiyi oluşturur ve webhook uç noktalarınızdan birine iletir; iletiyi uygulamanız gönderir (bkz. Webhook teslimi).webhook_endpoint_idalanına bu ortamdaki etkin bir uç noktanın kimliğini yazın.
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:
- İstek
POSTyöntemiyle,Content-Type: application/jsonve Standard Webhooks v1 üstbilgileriwebhook-id,webhook-timestampvewebhook-signatureile gönderilir.webhook-id, ileti kimliğidir (msg_…) ve aynı iletinin her denemesinde aynıdır; bu nedenle yinelemeleri bu kimliğe göre ayıklayın. Olay türünün uç noktanın olay aboneliğinde bulunması gerekmez: uç nokta, teslim uç noktası olduğu için olayı alır. - Başarı, herhangi bir
2xxyanıttır. Yönlendirmeler izlenmez ve başarısızlık sayılır. Tek bir deneme 10 saniyede zaman aşımına uğrar. - Başarısız bir deneme 15 saniye sonra yeniden denenir; toplamda en fazla 3 deneme yapılır. Üçüncü başarısızlıktan sonra ileti başarısız olarak işaretlenir ve e-posta gönderilmez. Yapılandırmayı kaydettikten sonra silinen ya da devre dışı bırakılan bir uç nokta, iletiyi deneme yapmadan hemen başarısız kılar.
- İleti içeriği webhook hizmeti tarafından saklanmaz. Yalnızca güvendiğiniz uç noktalara gönderin: gövde, alıcı adresini ve varsa oturum açma kodunu ya da bağlantısını içerir.
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.