Migrasi dari Postmark
Halaman ini memetakan payload pengiriman, ekspor supresi, dan webhook Postmark ke Bird. Ikuti panduan migrasi utama secara berurutan, dan gunakan pemetaan ini untuk langkah 1, 3, dan 4.
Dokumentasi Postmark menyatakan bahwa ia "does not currently support HMAC webhook signature verification" (dibaca September 2026), sehingga langkah 4 menambahkan verifikasi yang belum dimiliki handler Anda saat ini. Baca Terjemahkan event webhook sebelum Anda merencanakan cutover.
Serahkan ini ke agen Anda
Tempel ini ke Claude Code, Cursor, atau Codex. Agen akan mengerjakan halaman ini terhadap repositori Anda sendiri, menggunakan permukaan Bird mana pun yang sudah dimilikinya: server MCP jika sudah terhubung, CLI jika sudah terinstal dan masuk.
Contoh kode
I am moving an email integration from Postmark to Bird. Route through it with me.
1. Check what you already have before setting anything up. If Bird's MCP server is connected, use its tools. If the Bird CLI is installed and signed in, use that. Either one is enough, and every step below is an action you take with whichever you have. Only if neither is present, follow https://bird.com/docs/ai/set-up-your-agent.md to set one up and sign me in. Every Bird docs page serves Markdown at its own URL with `.md` appended, so fetch that rather than the HTML.
2. Read https://bird.com/docs/guides/email/migrate/postmark.md for the payload, suppression and webhook mapping, and https://bird.com/docs/guides/email/migrate.md for the order the steps go in.
3. Find and list my Postmark usage in this repository before you change anything: calls to /email and /email/withTemplate and any SDK wrappers around them, every MessageStream name I send on, the webhook handler and the URL it is registered at, and every domain I send from. Tell me the list before you edit anything.
4. Register each of those sending domains with Bird and give me the DNS records to publish, following https://bird.com/docs/guides/email/sending-domains.md. Leave every DNS record Postmark uses exactly as it is: Bird's records are published alongside them and both providers authenticate side by side until I switch traffic. Publishing DNS affects mail for the whole domain, so show me the records and let me publish them.
5. Export my suppressions and import them into Bird before any production traffic goes through Bird, so my first sends do not reach addresses that already bounced or complained. Postmark keeps suppressions per message stream, so dump GET /message-streams/{stream_id}/suppressions/dump once for every stream I send on rather than only the default outbound stream. The Bird import takes one address per request and is idempotent, so a partial re-run is safe. https://bird.com/docs/guides/email/suppressions.md has the reason taxonomy.
6. Port the send call and the webhook handler using the mapping tables on the provider page. Postmark's docs say it does not currently support HMAC webhook signature verification, so my handler probably has no signature check and adding one is new code rather than a swap. If my registered Postmark webhook URL carries HTTP Basic credentials, take them out and tell me to rotate that pair rather than reusing it on the Bird endpoint: a URL-embedded credential should be treated as exposed. https://bird.com/docs/guides/webhooks.md and https://bird.com/docs/guides/email/events.md.
7. Run my whole integration against Bird's mail sandbox before any production traffic, following https://bird.com/docs/guides/email/testing-sandbox.md. Sandbox sends run the real pipeline without reaching an inbox or touching my sending reputation.
8. Stop and ask me wherever a step needs a decision. Do not point production traffic at Bird until I have seen the sandbox results and replied with the words cut over to Bird. Retiring the Postmark path is a separate step that comes later: ask me again and wait for me to reply with the words retire the Postmark path. A reply that agrees without naming what it is authorising is not authorisation. Finish by telling me what is left that only a person can do.Petakan panggilan pengiriman
Postmark membagi pengiriman ke dua endpoint: POST /email untuk pesan yang disusun langsung dan POST /email/withTemplate untuk template tersimpan. POST /v1/email/messages kami adalah satu endpoint untuk keduanya, dengan template yang dirujuk dalam sebuah field.
| Fungsi | Postmark | Bird |
|---|---|---|
| Auth | Header X-Postmark-Server-Token | Authorization: Bearer |
| Pengirim | From | from |
| Penerima | To / Cc / Bcc (dipisah koma, maks 50) | to / cc / bcc (array) |
| Subjek | Subject | subject |
| Isi | HtmlBody / TextBody | html / text (minimal satu) |
| Reply-to | ReplyTo (dipisah koma) | reply_to (array) |
| Header kustom | Headers (objek Name/Value) | headers (objek string → string) |
| Label yang bisa difilter | Tag (satu per pesan) | tags: pasangan {name, value} |
| Konteks round-trip | Metadata | metadata: JSON arbitrer |
| Template tersimpan | TemplateId / TemplateAlias + TemplateModel | template + template.parameters |
| Pelacakan open | TrackOpens | track_opens (default true) |
| Pelacakan klik | TrackLinks (None/HtmlAndText/HtmlOnly/TextOnly) | track_clicks (boolean, lihat di bawah) |
| Lampiran | Attachments (Name, Content, ContentType) | attachments |
| Pemisahan trafik | MessageStream | (tidak ada padanan, lihat di bawah) |
| Kategori | (tidak ada) | category: marketing (default) atau transactional |
| Penjadwalan | (tidak ada) | scheduled_at |
Batas dan default field kami (jumlah penerima, batas tag dan metadata) ada di Mengirim email.
Catatan porting:
- Penerima adalah string di Postmark dan array di sini. "a@x.com, b@x.com" menjadi ["a@x.com", "b@x.com"]. Jika kode Anda membuat string tersebut dengan menggabungkan sebuah list, hapus join-nya, bukan list-nya.
- Tag adalah satu string per pesan. tags kami adalah pasangan, dan bisa lebih dari satu. Tag seperti "welcome" menjadi {"name": "category", "value": "welcome"}. Pilih name yang stabil agar dashboard Anda memfilter seperti statistik tag Postmark sebelumnya.
- Metadata bisa dipindahkan langsung, dan kami mengembalikannya. Kami mengembalikan metadata (dan tags) Anda di setiap event webhook bersama email_id/recipient_id, sehingga handler Anda mendapatkan konteksnya kembali tanpa perlu lookup.
- Pelacakan klik adalah enum di sana dan boolean di sini. TrackLinks: "None" adalah track_clicks: false; ketiga nilai yang aktif semuanya menjadi track_clicks: true, karena kami tidak melacak bagian HTML dan teks secara terpisah.
- Template masuk ke panggilan yang sama. Tidak ada endpoint template terpisah: TemplateId atau TemplateAlias menjadi template (berdasarkan ID atau slug) dan TemplateModel menjadi template.parameters, di POST /v1/email/messages. Lihat mengirim dengan template.
- Message stream tidak memiliki padanan, dan category bukan padanannya. Stream adalah wadah yang membawa daftar supresi, statistik, dan webhook-nya sendiri. Category kami adalah flag per pesan dengan satu efek: menentukan record supresi dan preferensi unsubscribe mana yang dapat memblokir pesan tersebut. Mengatur category: transactional karena pesan berasal dari stream transaksional biasanya benar, tetapi itu adalah pernyataan tentang alasan Anda mengirim, bukan porting dari stream. Tidak ada yang di sini mereproduksi statistik per stream atau cakupan supresi per stream; gunakan tag untuk pemisahan pelaporan.
Ekspor supresi
Postmark menyimpan supresi per message stream, sehingga tidak ada satu daftar tingkat akun yang bisa ditarik. Untuk setiap stream yang Anda gunakan untuk mengirim, ekspor dan jalankan hasilnya melalui loop impor:
- GET /message-streams/{stream_id}/suppressions/dump
Enumerasi stream Anda terlebih dahulu dan ekspor setiap stream yang masih Anda gunakan untuk mengirim. Memperlakukan stream outbound default seolah-olah itu satu-satunya daftar akan membawa supresi transaksional dan melewatkan yang broadcast, dan Anda baru mengetahuinya saat mengirim email ke orang yang sudah opt out. Nilai SuppressionReason adalah HardBounce, SpamComplaint, dan ManualSuppression, yang dipetakan ke alasan hard_bounce, complaint, dan manual kami. Supresi memiliki taksonomi lengkapnya.
Terjemahkan event webhook
Postmark mengirim satu tipe webhook per event dan mengidentifikasinya melalui field RecordType dalam payload.
| Hasil | Postmark | Bird |
|---|---|---|
| Diterima/diproses | (respons API) | email.accepted → email.processed |
| Terkirim | Delivery | email.delivered |
| Kegagalan sementara | Bounce dengan Type transien | email.deferred |
| Bounce permanen | Bounce dengan Type: HardBounce | email.bounced / email.out_of_band_bounce |
| Keluhan spam | SpamComplaint | email.complained |
| Diblokir/disupresi | (tidak ada) | email.rejected |
| Open | Open | email.opened |
| Klik | Click | email.clicked |
| Unsubscribe | SubscriptionChange | email.unsubscribed / email.list_unsubscribed |
Dua perbedaan menentukan seberapa banyak handler Anda harus berubah.
Verifikasi adalah kode baru, bukan penggantian. Dokumentasi Postmark menyatakan bahwa ia "does not currently support HMAC webhook signature verification" (dibaca September 2026), dan merekomendasikan kredensial HTTP Basic yang ditanamkan dalam URL terdaftar (https://<username>:<password>@example.com/webhook) ditambah rentang IP-nya di firewall Anda. Kami menandatangani setiap pengiriman sesuai skema HMAC Standard Webhooks, sehingga handler Anda mendapat langkah verifikasi yang belum dimilikinya. Caranya ada di Webhook dan event. Lakukan langkah ini terlebih dahulu: handler yang menerima permintaan tanpa tanda tangan adalah satu hal yang seharusnya tidak dibawa oleh migrasi.
Rotasi kredensial, jangan gunakan ulang. Username dan password yang pernah berada di dalam URL webhook harus dianggap sudah terekspos, karena URL masuk ke access log, ekspor konfigurasi, dan konsol vendor. Keluarkan dari endpoint dan terbitkan pasangan baru jika ada hal lain yang masih membutuhkannya; jangan bawa pasangan lama ke endpoint Bird, yang mengautentikasi dengan tanda tangan.
Postmark melaporkan hard bounce dan soft bounce sebagai satu record Bounce dengan field Type. Kami melaporkannya sebagai event yang berbeda. Handler yang bercabang berdasarkan Type di dalam payload bounce, di sini bercabang berdasarkan nama event: kegagalan sementara datang sebagai email.deferred dan yang permanen sebagai email.bounced. Setiap tipe bounce yang dibedakan Postmark ada di referensi Bounce API; yang penting untuk porting adalah di sisi mana dari pembagian itu masing-masing jatuh.
Event pengiriman kami bercakupan per penerima (recipient_id bersama email_id), sehingga pengiriman ke tiga penerima menghasilkan tiga hasil pengiriman, bukan satu.
Cutover
Ikuti domain dan DNS serta smoke test sandbox di panduan utama. Keduanya tidak bergantung pada provider.
Langkah selanjutnya
- Domain pengirim: registrasi, siklus verifikasi, dan record DNS yang Anda publikasikan
- Webhook dan event: pengaturan endpoint dan verifikasi Standard Webhooks
- Sandbox pengujian: smoke-test integrasi baru sebelum cutover
- Supresi: konfirmasi daftar yang sudah diimpor dan cara kami memeliharanya selanjutnya
Sumber daya terkait
Lanjutkan dengan dokumentasi, panduan, dan contoh untuk topik ini. Sumber daya tersedia dalam bahasa Inggris.
Tonton panduannyaGetting started with emailJelajahi kemampuannyaEmailIkuti jalur pembelajaranBuild your first integrationPanduan implementasiSend your first email
Coba praktiknya dan dapatkan ringkasan implementasi