# Migrasi dari Postmark

Halaman ini memetakan payload pengiriman, ekspor supresi, dan webhook Postmark ke Bird. Ikuti [panduan migrasi utama](/docs/guides/email/migrate) 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](#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.

```text
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`](/docs/api/reference/create-email-message) 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](/docs/guides/email/sending-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](/docs/guides/email/sending-email#sending-with-a-template).
- **Message stream tidak memiliki padanan, dan `category` bukan padanannya.** Stream adalah wadah yang membawa daftar supresi, statistik, dan webhook-nya sendiri. [Category](/docs/guides/email/categories) 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](/docs/guides/email/sending-email#tags-vs-metadata) 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](/docs/guides/email/migrate#3-import-suppressions):

- `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](/docs/guides/email/suppressions) 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](https://www.standardwebhooks.com), sehingga handler Anda mendapat langkah verifikasi yang belum dimilikinya. Caranya ada di [Webhook dan event](/docs/guides/webhooks). 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](/docs/guides/email/migrate#2-re-point-domains-and-dns) serta [smoke test sandbox](/docs/guides/email/migrate#5-verify-in-the-sandbox-before-cutover) di panduan utama. Keduanya tidak bergantung pada provider.

## Langkah selanjutnya

- [Domain pengirim](/docs/guides/email/sending-domains): registrasi, siklus verifikasi, dan record DNS yang Anda publikasikan
- [Webhook dan event](/docs/guides/webhooks): pengaturan endpoint dan verifikasi Standard Webhooks
- [Sandbox pengujian](/docs/guides/email/testing-sandbox): smoke-test integrasi baru sebelum cutover
- [Supresi](/docs/guides/email/suppressions): konfirmasi daftar yang sudah diimpor dan cara kami memeliharanya selanjutnya

## Related resources

- [Getting started with email](/learn/email/getting-started-with-email) (video)
- [Email](/products/email) (product)
- [Build your first integration](/learn/paths/integration) (course)
- [Send your first email](/docs/get-started/send-your-first-email) (docs)

[Get an implementation brief](/learn/workspace?topic=email)
