OneSignal'dan geçiş
Open Notification bilerek OneSignal'ın zihinsel modelini yansıtır — external id'li ve tag'li kullanıcılar, login/logout, segmentler, kampanya sihirbazı, ulaştı/açıldı/tıklandı raporları — dolayısıyla ekibinizin bildiği çoğu şey çalışmaya devam eder. Geçişin zor kısmı API değil, token'lardır.
Ne taşınabilir
| Veri | Taşınır | Neden |
|---|---|---|
| APNs cihaz token'ları | ✅ | Bundle ID'nize bağlıdır; kendi .p8'iniz onlara gönderir. |
| FCM registration token'ları | ✅ | Sizin Firebase projenize aittir — OneSignal sizin service account'unuzu kullanıyordu. |
| Web push abonelikleri | ❌ | Endpoint'ler OneSignal'ın VAPID public key'ine bağlıdır; dışa aktarmazlar. Yeniden toplayın. |
| Tag'ler, external id'ler, dil, saat dilimi, ülke | ✅ | CSV export'tan. |
| Kampanya geçmişi | ⚠️ | Yalnızca toplam sayılar, olay bazında değil. |
Her tarayıcı aboneliği, oluşturulduğu VAPID anahtarına kriptografik olarak bağlıdır. Kesimden haftalar önce, OneSignal göndermeye devam ederken kendi anahtarınızla abonelik toplamaya başlayın — ikisi yan yana sorunsuz çalışır.
Kavram eşlemesi
| OneSignal | Open Notification |
|---|---|
| App | Uygulama |
User / external_id | externalId'li Kullanıcı |
| Subscription (push token) | Abonelik |
OneSignal.login(id) / logout() | login(externalId) / logout() — aynı merge semantiği |
addTags / removeTags | setTags (merge) / removeTags (null siler) |
| Segmentlerde data tag | segment oluşturucuda tags.<key> alanları |
| Kayıtlı segmentler | Segmentler kampanya başına satır içidir; yeniden kullanacaksanız JSON'u kendiniz saklayın |
| Message → "Send to particular users" | target.externalIds ile POST /v1/notifications |
| Intelligent delivery | schedule.type = "user_timezone" |
| Frequency capping | Teslimat kuralları |
| A/B test | A/B testi |
| REST API key | sk_live_… sunucu anahtarı |
| App ID (client) | pk_live_… public anahtar |
| Notification Service Extension | iOS'ta hâlâ gerekli — bizimki iOS SDK'sında şablon olarak gelir |
CSV export'tan token içe aktarma
OneSignal export'unda şu sütunlar var: identifier (push token), device_type (0 iOS, 1 Android, 5 Chrome web…), external_user_id, tags, language, timezone_id, country, last_active, notification_types (-2 = çıkmış).
Paneli kullanın: Abonelikler › İçe aktar, biçim OneSignal, dosyayı bırakın. Her satır SDK'ların kullandığı upsert'ten geçer, kullanıcılar ve tag'ler doğru çıkar; çıkmış ve geçersiz satırlar atlanır, anahtarlarıyla web satırları da aktarılır, sonuç kartı reddedilen satırları listeler. Bkz. Abone içe aktarma.
Betikle yapmayı yeğlerseniz (özel bir export, yolda bir dönüşüm) public API aynı işi satır başına bir POST /v1/subscriptions ile yapar:
// import-onesignal.ts — bun ile çalıştırın
import { parse } from "csv-parse/sync";
const rows = parse(await Bun.file("players.csv").text(), { columns: true });
const API = "https://push.example.com";
const KEY = process.env.PUBLIC_KEY!; // pk_live_…
for (const row of rows) {
if (!row.identifier || row.notification_types === "-2") continue;
if (row.device_type !== "0" && row.device_type !== "1") continue; // web içe aktarılamaz
const tags = row.tags ? JSON.parse(row.tags) : {};
const res = await fetch(`${API}/v1/subscriptions`, {
method: "POST",
headers: { "x-app-key": KEY, "content-type": "application/json" },
body: JSON.stringify({
platform: row.device_type === "0" ? "ios" : "android",
token: row.identifier,
externalId: row.external_user_id || undefined,
tags,
language: row.language || undefined,
timezone: row.timezone_id || undefined,
country: row.country || undefined,
}),
});
if (!res.ok) console.error(row.identifier.slice(-6), await res.text());
}
Hızı sınırlayın: public anahtar sır olmadığı için subscribe ucu istemci IP'si başına sınırlıdır (SUBSCRIBE_RATE_LIMIT, varsayılan 10/dk). İçe aktarım için sınırı geçici olarak yükseltin ya da script'i API sunucusunun kendisinde çalıştırın.
İlk gerçek kampanyadan önce doğrulayın
İçe aktarımdaki ölü token'lar ilk teslimat oranınızı berbat gösterir. İçe aktardıktan sonra herkese bir sessiz kampanya gönderin (sihirbazda Sessiz push ya da boş data ile "silent": true) — worker yol üstünde her 410/Unregistered/UNREGISTERED aboneliği geçersizler ve raporun Başarısızlık nedenleri tablosu export'un ne kadarının zaten ölü olduğunu söyler.
Paralel çalışma planı
Hafta 1–2 Open Notification SDK'sını OneSignal'ınkinin yanına ekleyin. Token
toplar, olay raporlar; henüz onunla bir şey göndermezsiniz.
Sürümü yayınlayıp çoğu kullanıcıya ulaşmasını bekleyin.
Hafta 3 Oturumların ~%90'ı yeni sürümdeyken kalan OneSignal token'larını
CSV'den içe aktarın (API token'a göre upsert eder; SDK'nın zaten
kaydettiği cihazlar yalnızca güncellenir).
Hafta 4 Trafiği kaydırın: kampanyaların %10 → %50 → %100'ü. Her adımda
platform başına teslimat oranını OneSignal'ınkiyle karşılaştırın.
Hafta 5 OneSignal SDK'sını kaldırın. Hesabı kapatın.
iOS'ta iki SDK demek APNs token'ını iki şeyin istemesi demek — sorun değil; ikisi de aynı token'ı alır. Android'de ikisi de bir FirebaseMessagingService kaydeder; yalnızca biri mesaj alabilir. Ya OneSignal'ın servisinden onMessageReceived'i OpenNotificationMessagingService.handle(message)'a iletin ya da Android gönderimlerini 4. haftaya kadar OneSignal'da tutun.
Bilerek farklı olanlar
- Açılma oranı sent'e değil delivered'a bölünür. OneSignal'ın rakamları aynı kampanya için bizimkinden yüksek görünür. Dürüst olan bizimki: hiç ulaşmayan bir push açılamaz.
- Android mesajları data-only'dir. Bildirimi uygulamanız çizer (SDK yapar);
delivered'ı izlenebilir kılan budur. Zorla durdurulmuş uygulamalar ve agresif OEM pil tasarrufu data mesajlarını düşürebilir; SDK bunu en aza indirmek içinpriority: highile gönderir. - E-posta, SMS ya da in-app kanalı yok. Yalnızca push.
- Segmentler kayıtlı nesne değildir. Kampanya kendi segment JSON'unu taşır; sihirbazdan kampanyalar arasında kopyalayın ya da kendi kodunuzda tutun.