Ürün bağlantısından hazır e-posta bloğuna

Ürün bağlantısından başlık, fiyat ve görsel alan e-posta bloğunu nasıl geliştirdim? Örnek veriden gerçek servise geçiş ve düzenleme hatalarının çözümü.

Blok paleti, yapıştırılan URL'den dolan iki sütunlu ürün bloğu ve inspector paneli olan e-posta editörü

Bir kampanya e-postasına ürün koymak eskiden elle kopyalamak demekti. Ürün sayfasını aç, başlığı kopyala, fiyatı kopyala, görseli indir, yükle, linki bir butonun altına yapıştır. Sonra aynısını bir sonraki ürün için yap.

Bir de fiyatlar değişiyor. Gönderilmiş bir e-postadaki yanlış fiyat bir destek talebi demek ve e-postayı geri çekemiyorsunuz.

İstek basitti: bir ürün URL'i yapıştır, hazır bir ürün bloğu al. Bunu Ocak ile Mayıs 2026 arasında, Revotas panelindeki sürükle-bırak e-posta editörü için yeni bir blok olarak yaptım.

Bir ürün URL’i yapıştır, blok dolu dönsün: görsel, başlık, fiyat ve butonun altındaki link.

Editör benim değildi

Panelin e-posta editörü açık kaynak bir React editörü (Easy Email, çıktıyı MJML üzerinden üretiyor). Kendi blok kaydı, kendi inspector (seçilen öğenin ayarlarını gösteren panel) panelleri ve kendi sürükle-bırak sistemi var. Fork (mevcut çalışmadan bağımsız ilerleyen bir kopya oluşturma) etmek, her güncellemeyi bir merge'e (ayrı geliştirme dalındaki değişikliği ana kodla birleştirme) çevirirdi.

Bu yüzden blok tamamen editörün zaten sunduğu genişleme noktalarının içinde yaşamalıydı:

  • yeni blok tiplerinin kaydedildiği bir block manager,
  • her blok tipini kendi inspector'ına eşleyen bir configuration manager,
  • palette neyin görüneceğini belirleyen kategori listeleri.

Ürün verisi benim yazmadığım bir backend (uygulamanın sunucuda çalışan bölümü) endpoint'inden (bir serviste istek gönderilen belirli adres) geliyor. Ona bir ürün URL'i ve bir marka veriyorsunuz; URL'i o markanın ürün kataloğunda arıyor ve başlık, fiyat, görsel ve açıklama döndürüyor. Tarayıcıda olan her şey benim tarafımdı.

Son commit'imde (kod deposuna kaydedilen değişiklik paketi) blok altı dosyada yaklaşık 1.8k satırdı ve bunun 1.200'den fazlası inspector'dı.

Önce mock, sonra gerçek API

İlk sürüm (Ocak sonu) bir blok ve 148 satırlık bir inspector'dı. Şubat başındaki ikinci sürüm iki ve üç sütunlu şablonları ekledi ve tamamen mock (gerçek servis veya verinin yerini alan test örneği) veriyle çalıştı: public klasöründe sahte bir ürün sayfası ve küçük bir mock ürün modülü.

Bu bilinçliydi. UX'in gerçek veriye dokunmadan önce gözden geçirilmesini istedim. Gerçek istek daha bağlanırken ekip bloğu sürükleyip bir URL yapıştırabiliyor ve dolmasını görebiliyordu.

İki hafta sonra gerçek endpoint'e geçtim ve mock fallback'i (ilk seçenek işe yaramadığında kullanılan yedek yol) aynı commit'te sildim. "Ne olur ne olmaz" diye tutmak riskli geldi. Bir e-posta editörünün içinde sessizce sahte veriye düşmek, yer tutucu bir ürünün gerçek bir kampanyaya girmesinin en kolay yolu.

Geçiş, mock'un asla göstermeyeceği şeyleri ortaya çıkardı. Endpoint eşleşmeleri bir dizi olarak döndürüyor; kodun dizi, düz obje ya da JSON (veriyi alan ve değer çiftleriyle taşıyan metin biçimi) string (metin değeri) ile başa çıkması gerekiyor ve alanlar birkaç farklı şekilde yazılabiliyor (title ya da product_name, imageUrl ya da image_link). Bu yüzden küçük bir açma adımı ve bilinen her yazımı beş alana eşleyen bir normalizer (farklı biçimlerdeki veriyi ortak bir yapıya çeviren işlev) var. Beş alandan hiçbiri dolu gelmezse bu bir hata sayılıyor ve kullanıcı bir mesaj görüyor.

Başkasının inspector'ını tam bir kez sarmak

Editörün ilginç kısmı inspector. Bir sütuna tıkladığınızda editör o sütunun ayarlarını gösteriyor. Ben de ürün tutan her sütunda ürün panelinin bu ayarların altında çıkmasını istedim.

Configuration manager bir blok tipinin inspector'ını değiştirmeye izin veriyor. Ben de orijinal sütun inspector'ını okudum, orijinali ve kendi panelimi birlikte çizen bir bileşenle sardım ve sarmalayıcıyı kaydettim.

İki şey dikkat istedi.

Birincisi, kayıt editör sayfasındaki bir effect içinde çalışıyor ve o sayfa her açılışta yeniden mount oluyor. Koruma olmasa her mount bir önceki sarmalayıcıyı tekrar sarar ve ürün panelinin bir kopyasını daha üst üste koyardı. Bu yüzden modül seviyesinde bir isRegistered kontrolü ve sarmalayıcı bileşenin üstünde bir bayrak var. Kod sarmadan önce, az önce okuduğu inspector'da bu bayrak var mı diye bakıyor. Varsa dokunmuyor. Bayrak ikisinin daha sağlamı, çünkü bileşenle birlikte taşınıyor.

İkincisi, editörün lookup'ı her zaman bir fonksiyon döndürmüyor. Blok tipine göre bileşenin kendisini ya da component veya render alanı olan bir objeyi döndürüyor. İlk sürümüm fonksiyon varsaydı, bazı tiplerde hiçbir şey bulamadı ve o sütunlar yanlış paneli gösterdi. Üç şekli de kontrol eden küçük bir çözümleyici bu uyuşmazlığı düzeltti.

İsteği bozan çerez

Panel ile ürün endpoint'i farklı origin'lerde. İlk isteğim credentials gönderiyordu, çünkü paneldeki neredeyse her çağrı öyle yapıyor.

Bu çalışamazdı. Endpoint wildcard bir CORS (tarayıcının başka bir alan adındaki veriye erişimini düzenleyen kurallar) origin'iyle cevap veriyor ve tarayıcılar wildcard altında credentials'lı bir cevabı sayfaya vermiyor. Endpoint'in zaten oturuma ihtiyacı yok: marka id'si query parametresi olarak gidiyor. Düzeltme tek satırdı: withCredentials: false.

Panel yine de bir şey göndermeden önce markayı bildiğinden emin oluyor. Kayıtlı oturumda marka yoksa, işe yaramayacak bir istek yerine net bir "müşteri bulunamadı" mesajı çıkıyor.

Aynı hafta URL kontrolünü de değiştirdim. İlk sürüm başta http:// ya da https:// arayan bir regex'ti (metin içindeki örüntüleri bulmaya yarayan eşleştirme kuralı). Şimdi URL constructor'ından geçiyor ve yalnızca bu iki protokolü kabul ediyor. https:// ile başlayıp devamı anlamsız olan bir metin artık geçmiyor.

Kendine yazıp duran blok

Yeni bir ürün sütununda inspector'ı açmak varsayılan değerleri dolduruyor: boş bir ürün, varsayılan buton metni, açık durum. Panel bunları bloğa yazıyor.

Bloğa yazmak inspector'ı yeniden render (veriyi ekranda gösterilecek görünüme dönüştürme) ediyor. Yeniden render bloğu gördü, varsayılanları tekrar hesapladı (yeni bir obje, aynı içerik) ve tekrar yazdı. Bir döngü. Hiç çökmedi; sadece aynı değerleri yazmaya devam etti.

Düzeltme her yazmadan önce bir karşılaştırmaydı. Sonraki değer alan alan mevcut değerle aynıysa yazma atlanıyor. On beş satır.

Nerede olduğunu bilen inspector

Blok, sütunu editörün sıradan bloklarıyla dolduruyor: bir görsel, başlık, fiyat ve açıklamanın olduğu bir metin bloğu ve ürüne giden bir buton. Bu bilinçli. Pazarlamacı fontu ya da buton rengini zaten bildiği araçlarla değiştirebiliyor.

Son gerçek hatayı da bu çıkardı. Sütun dolduktan sonra kullanıcı değiştirmek için ürün görseline tıklıyor. Artık odakta görsel bloğu var, editör görsel inspector'ını gösteriyor ve ürün paneli kayboluyor. Yeniden çekmek ya da URL'i değiştirmek için sütunun kendisini bulup tıklamanız gerekiyordu.

Mayıs sürümü bunu düzeltti. Metin ve görsel inspector'ları da sütunlar gibi sarılıyor ve panel odaktaki bloktan ebeveynlerine doğru yukarı çıkıyor. Atalardan biri ürün bloğuysa ya da ürün verisi tutan bir sütunsa, panel o sütunun verisiyle görünüyor. Tek bir ürün sütunu olan bir section da sayılıyor.

Nisan ve Mayıs sürümleriyle iki küçük kural daha geldi:

  • Input'lar (girdi) controlled ve her yazmadan önce kısa bir 120 ms gecikme var. Böylece yazarken her tuşa basış bir blok güncellemesi tetiklemiyor.
  • Panel, çekilen içeriği elle düzenleyip düzenlemediğinizi takip ediyor. Düzenlediyseniz tekrar çekmek, düzenlemelerinizin üstüne yazmadan önce soruyor.

Dört varyant, üç kategori

Palette sonunda dört varyant oldu: temel blok, artı eşit sütunlu bir section oluşturup her sütuna bir ürün bloğu koyan bir, iki ve üç ürünlük şablonlar. Content, Layout (öğelerin ekrandaki yerleşimi) ve Ecommerce olmak üzere üç palet kategorisinde duruyorlar; Ecommerce'te özel önizleme kartlarıyla.

Boş bir ürün bloğu editörde parlayan bir yer tutucu gösteriyor ("Product card will appear here") ve dışa aktarılan e-postada hiçbir şey render etmiyor. Yarım kalmış bir blok asla gönderilmiyor.

Farklı ne yapardım

İlk satırdan önce editörün genişleme sözleşmesini yazıya dökerdim. Kayıt uyuşmazlığı doğrudan configuration manager'ın ne döndürdüğünü tahmin etmekten çıktı.

Bir de garip layout'lardan oluşan bir fixture'ı (test için önceden hazırlanmış örnek veri) erkenden hazırlardım: bir wrapper (başka bir bileşeni sararak ek davranış kazandıran katman) içindeki section içindeki ürün sütunu, görseli elle değiştirilmiş bir sütun. Güncelleme döngüsü ve kaybolan panel aylar sonra değil, ilk haftalarda ortaya çıkardı.

Sonradan iki ekip arkadaşım bunun üstüne bir şeyler kurdu. Biri metin inspector'ını bir HTML (web sayfasının yapısını tanımlayan işaretleme dili) modu editörüyle değiştirdi; benim kodum o anda kayıtlı olan inspector'ı sardığı için sadece doğru çağrı sırası yetti. Diğeri yanıttaki alternatif ürün görselleri arasında seçim yapan bir alan ekledi. İkisi de kayıt koduma dokunmadı ve en çok sevindiğim kısım bu.

Etkileşimli demoyu deneyin. Sentetik ürünlerle çalışıyor ve linklerden biri bilerek hata veriyor. Diğer işlerim Revotas sayfasında.

Bu yazı İngilizce aslından Türkçeye çevrilmiştir.

Çözmeye değer bir problemin mi var?Birlikte çözelim

Konuşmaya başla

Full-stack ve uygulamalı AI rollerine açığım, uzaktan veya hibrit.

Rolü, ekibi ve problemi anlat.