Bir kutu aç, adresini postayı gönderecek yere ver, sonra bekle. Bekleme çağrısı mesaj düştüğü anda döner; kod da ayıklanmış olarak gelir.
API=https://nrthmail.com/api/v1
# 1 · Kutu aç. Token yalnızca bir kez gösterilir: sakla.
NEW=$(curl -s -X POST $API/inboxes)
TOKEN=$(echo "$NEW" | jq -r .token)
SINCE=$(echo "$NEW" | jq -r .inbox.created_at)
echo "$NEW" | jq -r .inbox.address # quiet.fjord47@nrthmail.com
# 2 · Adresi bir yerde kullan, sonra postayı 60 sn’ye kadar bekle.
ID=$(curl -s "$API/inbox/wait?timeout=60&since=$SINCE" \
-H "Authorization: Bearer $TOKEN" | jq -r '.messages[0].id')
# 3 · Bir mesajın tamamını oku.
curl -s $API/inbox/messages/$ID -H "Authorization: Bearer $TOKEN" \
| jq '{subject, codes, links}'
const API = 'https://nrthmail.com/api/v1';
// 1 · Kutu aç. Token yalnızca bir kez gösterilir: sakla.
const { token, inbox } = await fetch(`${API}/inboxes`, { method: 'POST' }).then((r) => r.json());
const auth = { Authorization: `Bearer ${token}` };
console.log(inbox.address); // quiet.fjord47@nrthmail.com
// 2 · Adresi bir yerde kullan, sonra postayı 60 sn’ye kadar bekle.
const since = encodeURIComponent(inbox.created_at);
const waited = await fetch(`${API}/inbox/wait?timeout=60&since=${since}`, { headers: auth })
.then((r) => r.json());
// 3 · Bir mesajın tamamını oku.
if (!waited.timed_out) {
const id = waited.messages[0].id;
const { message } = await fetch(`${API}/inbox/messages/${id}`, { headers: auth }).then((r) => r.json());
console.log(message.subject, message.codes[0]);
}
import requests
API = "https://nrthmail.com/api/v1"
# 1 · Kutu aç. Token yalnızca bir kez gösterilir: sakla.
new = requests.post(f"{API}/inboxes", timeout=10).json()
auth = {"Authorization": f"Bearer {new['token']}"}
print(new["inbox"]["address"]) # quiet.fjord47@nrthmail.com
# 2 · Adresi bir yerde kullan, sonra postayı 60 sn’ye kadar bekle.
waited = requests.get(f"{API}/inbox/wait", headers=auth, timeout=75,
params={"timeout": 60, "since": new["inbox"]["created_at"]}).json()
# 3 · Bir mesajın tamamını oku.
if not waited["timed_out"]:
msg_id = waited["messages"][0]["id"]
message = requests.get(f"{API}/inbox/messages/{msg_id}", headers=auth, timeout=10).json()["message"]
print(message["subject"], message["codes"][:1])
curl örneği JSON’dan alan seçmek için jq kullanır.
02
Temeller
Kimlik doğrulama
Kutu açınca nrth_ ile başlayan bir token döner. Her kutu çağrısında onu Authorization: Bearer nrth_… olarak gönder. Var olan tek kimlik bilgisi budur ve elinde tutan kutuyu okuyabilir. Yalnızca bir kez gösterilir; sunucu yalnızca SHA-256 özetini saklar.
Biçim
Giriş ve çıkış JSON, alan adları snake_case. Zaman damgaları milisaniyeli, UTC, RFC 3339: 2026-10-03T19:44:19.123Z. Her yanıt Cache-Control: no-store taşır.
CORS
API’yi her kaynak çağırabilir: GET POST PATCH DELETE yöntemleri, Authorization ve Content-Type başlıkları; Location başlığı okunabilir. Tarayıcı kodu doğrudan konuşabilir.
Ömür
Bir kutu son kullanımından 24 saat sonrasına kadar yaşar. Kimlik doğrulamalı her istek ve açık her olay akışı expires_at değerini ileri iter; POST /inbox/extend bunu bilerek yapar. Süre dolunca mesajlar ve dosyalar hemen silinir.
Ücret
Yok. Anahtar, hesap, paket ya da istek sınırı yok. Tek sınırlar depolamayla ilgili; Sınırlar bölümünde listeli.
03
Kutular
POST/api/v1/inboxes
Kutu aç. Gövde göndermezsen quiet.fjord47 gibi rastgele bir isim alırsın; istersen JSON ile name ve isteğe bağlı domain gönder. 201 ile token’ı, kutuyu ve Location: /api/v1/inbox başlığını döner.
Ad
Yer
Tür
Not
name
gövde
string
3–40 karakter, a–z 0–9 . _ -; harf ya da rakamla başlar ve biter. Rastgele isim için gönderme.
domain
gövde
string
GET /domains listesinden biri. Varsayılan: ilki.
İsim alınmış ya da hâlâ kilitliyse 409 address_unavailable; geçersiz isim ya da bilinmeyen alan adında 422 validation_failed; postmaster gibi isimlerde 422 name_reserved.
Süresi dolmuş bir kutuyu eski token’ıyla geri getir. Boş olarak, taze 24 saatle döner. Süre dolduktan sonraki 30 gün boyunca çalışır. Yakılmış kutular geri gelmez: 410 gone. Bilinmeyen token 404 alır.
Tek bir ek, özgün dosya adıyla ve X-Content-Type-Options: nosniff ile indirme olarak.
05
Uzun yoklama
GET/api/v1/inbox/wait
Bir betikte posta almanın en basit yolu: eşleşen bir mesaj gelene ya da süre dolana kadar bekleyen tek bir istek. since değerinden yeni, eşleşen mesajlar zaten varsa hemen döner.
Ad
Yer
Tür
Not
timeout
sorgu
integer
1–60 saniye, varsayılan 30.
since
sorgu
string
RFC 3339 zaman ya da mesaj kimliği. Varsayılan: yok; kutuda zaten olan her mesaj sayılır. Böylece yeni bir kutu, posta beklemeye başlamadan önce düşmüş olsa bile ilk mesajını döndürür.
from
sorgu
string
Gönderende geçmesi gereken metin.
subject
sorgu
string
Konuda geçmesi gereken metin.
Döner: {"messages":[Message…],"timed_out":false,"waited_ms":4210}. Mesajlar tam Message nesneleridir.
HTTP istemcine timeout değerinden uzun bir zaman aşımı ver. Hiçbir mesajı kaçırmadan döngü kurmak için gördüğün en yeni kimliği since olarak gönder.
# GitHub’dan gelecek bir sonraki mesajı 60 sn’ye kadar bekle.
curl -s "https://nrthmail.com/api/v1/inbox/wait?timeout=60&from=github" \
-H "Authorization: Bearer $TOKEN"
const API = 'https://nrthmail.com/api/v1';
const auth = { Authorization: 'Bearer nrth_…' };
// Beklemeye devam et, hiçbirini kaçırma.
let since = new Date().toISOString();
for (;;) {
const url = `${API}/inbox/wait?timeout=60&since=${encodeURIComponent(since)}`;
const { messages } = await fetch(url, { headers: auth }).then((r) => r.json());
for (const m of messages) console.log(m.subject, m.codes);
if (messages.length) since = messages.map((m) => m.id).sort().at(-1);
}
import requests
from datetime import datetime, timezone
API = "https://nrthmail.com/api/v1"
auth = {"Authorization": "Bearer nrth_…"}
# Beklemeye devam et, hiçbirini kaçırma.
since = datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
while True:
r = requests.get(f"{API}/inbox/wait", headers=auth, timeout=75,
params={"timeout": 60, "since": since})
messages = r.json()["messages"]
for m in messages:
print(m["subject"], m["codes"])
if messages:
since = max(m["id"] for m in messages)
06
Canlı olaylar (SSE)
GET/api/v1/inbox/events
Her yeni mesajı düştüğü anda ileten bir Server-Sent Events akışı. Yanıtı akış olarak okuyabilen koddan (fetch, requests, curl) Authorization başlığıyla aç. Tarayıcıdaki EventSource başlık gönderemez: önce bir bilet al ve ?ticket= ile ilet.
Başlık yerine POST /inbox/tickets ile alınan bilet.
Last-Event-ID
başlık
string
Yeniden bağlanınca bu kimlikten yeni mesajları tekrar gönderir.
Akış retry: 3000 ile başlar ve 20 saniyede bir : ping yorumu gönderir; her ping kutuyu da canlı tutar. Yeniden bağlanırken Last-Event-ID gönderirsen kaçırdığın mesajlar tekrar gelir. Yanıt başlıkları: Content-Type: text/event-stream, Cache-Control: no-cache, no-transform, X-Accel-Buffering: no.
const API = 'https://nrthmail.com/api/v1';
const token = 'nrth_…';
// fetch ile akış: token başlıkta kalır.
const res = await fetch(`${API}/inbox/events`, {
headers: { Authorization: `Bearer ${token}`, Accept: 'text/event-stream' },
});
const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
let buffer = '';
for (;;) {
const { value, done } = await reader.read();
if (done) break;
buffer += value;
let end;
while ((end = buffer.indexOf('\n\n')) >= 0) {
const block = buffer.slice(0, end);
buffer = buffer.slice(end + 2);
let event = 'message', data = '';
for (const line of block.split('\n')) {
if (line.startsWith('event:')) event = line.slice(6).trim();
else if (line.startsWith('data:')) data += line.slice(5).trim();
}
if (event === 'message' && data) console.log(JSON.parse(data).codes);
}
}
// Biletle, tarayıcının kendi EventSource’u.
const { ticket } = await fetch(`${API}/inbox/tickets`, {
method: 'POST', headers: { Authorization: `Bearer ${token}` },
}).then((r) => r.json());
const source = new EventSource(`${API}/inbox/events?ticket=${ticket}`);
source.addEventListener('message', (e) => console.log(JSON.parse(e.data).subject));
source.addEventListener('expired', () => source.close());
// Biletler tek kullanımlık: hata olursa yenisini alıp yeniden bağlan.
source.onerror = () => source.close();
import json
import requests
API = "https://nrthmail.com/api/v1"
auth = {"Authorization": "Bearer nrth_…", "Accept": "text/event-stream"}
# requests ile akış.
with requests.get(f"{API}/inbox/events", headers=auth, stream=True, timeout=(10, 90)) as r:
event, data = "message", ""
for line in r.iter_lines(decode_unicode=True):
if line.startswith("event:"):
event = line[6:].strip()
elif line.startswith("data:"):
data += line[5:].strip()
elif line == "":
if event == "message" and data:
print(json.loads(data)["codes"])
event, data = "message", ""
POST/api/v1/inbox/tickets
EventSource için tek kullanımlık, 60 saniye geçerli bilet. Her yeniden bağlanmada yenisini al. Döner: {"ticket":"tk_…","expires_at":"…"}
07
Paylaşım ve QR
Bir kutuyu token’ı URL’ye koymadan başka bir cihaza taşı. Paylaşım kodu tek kullanımlıktır ve 120 saniye yaşar; diğer tarafta kullanıldığında token’ı döner.
POST/api/v1/inbox/share
Paylaşım kodu oluşturur. url değeri https://nrthmail.com/#share=CODE biçimindedir; kod, tarayıcıların sunucuya hiç göndermediği parça (fragment) kısmındadır. Döner: {"code","url","expires_at"}
POST/api/v1/share/{code}
Paylaşım kodunu kullanır. Token gerekmez. Token’ı ve kutuyu döner; kod bilinmiyorsa, kullanılmışsa ya da süresi dolmuşsa 404.
GET/api/v1/inbox/qr.svg
Taze bir paylaşım URL’sinin QR kodu (SVG). Her çağrı yeni bir kod üretir; X-Share-Expires ne zaman geçersiz olacağını söyler. Modüller şeffaf zemin üzerinde #000: açık renkli bir yüzeyde göster.
<a>, <img> ve <iframe>Authorization başlığı gönderemez; bu yüzden tam bir Message imzalı bağlantılar taşır: html_url, raw_url ve her ek için bir url. Biçimleri /api/v1/files/{raw|att|html}/{id}?exp=…&sig=… şeklindedir ve bir saat çalışırlar. Sonra 403 forbidden döner; taze bağlantılar için mesajı yeniden çek.
html_url, temizlenmiş e-postayı sıkı bir sandbox CSP altında, iframe’e hazır tam bir HTML belgesi olarak sunar. Betikler, formlar ve eklentiler çıkarılır, bağlantılar yeni sekmede açılır; ?images=true istemedikçe uzak görseller engelli kalır.
09
Nesneler
Inbox
Alan
Tür
Açıklama
id
string
ibx_ + ULID.
address
string
Tam adres, küçük harfle.
local_part
string
@ işaretinden önceki kısım.
domain
string
@ işaretinden sonraki kısım.
custom
boolean
İsim üretilmeyip seçildiyse true.
status
string
active, expired ya da burned.
created_at
time
Kutunun açıldığı an.
expires_at
time
Yeniden kullanılmazsa silineceği an.
ttl_seconds
integer
Son kullanımdan sonraki ömür, saniye olarak.
message_count
integer
Kutudaki mesaj sayısı.
unseen_count
integer
Henüz görüldü işaretlenmemiş mesajlar.
MessageSummary
Alan
Tür
Açıklama
id
string
msg_ + ULID. Geliş sırasına göre sıralanır.
seq
integer
Kutuya özgü sayaç: 1, 2, 3…
received_at
time
Mesajın geldiği an.
from
{name, address}
From başlığındaki gönderen. name null olabilir.
to
string
Mesajın gönderildiği adres, varsa +etiket dahil.
tag
string · null
+etiket kısmı ya da null.
subject
string · null
Konu satırı ya da null.
preview
string
Metnin ilk 160 karakteri.
seen
boolean
Görüldü olarak işaretlenip işaretlenmediği.
size
integer
Ham mesajın bayt cinsinden boyutu.
has_attachments
boolean
Eki olup olmadığı.
attachment_count
integer
Ek sayısı.
codes
string[]
İçinde bulunan en fazla 3 doğrulama kodu, en olası önce.
Temizlenmiş HTML gövde. HTML kısmı yoksa ya da zamanında işlenemeyecek kadar karmaşıksa null (text alanını kullan). ?images=true yoksa uzak görseller engellidir.
html_url
string · null
Temizlenmiş HTML’in tam belge olarak imzalı URL’si; iframe için. Mesajın HTML kısmı yoksa null.
remote_images
{blocked, count, trackers}
Uzak görsellerin engellenip engellenmediği, kaç tane olduğu ve kaçının izleyiciye benzediği.