Skip to content

fix(drivers): postmark drops the value of the first tag, unlike every other driver #126

Description

@productdevbook

What happens

Postmark takes exactly one Tag, and the driver handles the overflow by moving tags 2..n into Metadata:

if (msg.tags.length > 0) {
  payload.Tag = msg.tags[0]!.name
  if (msg.tags.length > 1) {
    payload.Metadata = { ...payload.Metadata, ...Object.fromEntries(msg.tags.slice(1).map((t) => [t.name, t.value])) }
  }
}

The first tag's name becomes Tag; its value is read nowhere. The comment on those lines says "the rest carry as metadata so nothing the caller set is silently dropped" — but tag[0].value is exactly what gets silently dropped, and only tag[0].

Reproduction

import { createEmail } from "../src/core/email.ts"
import postmark from "../src/drivers/postmark.ts"

let body: any
const f = (async (_u: any, init: any) => {
  body = JSON.parse(init.body)
  return new Response(JSON.stringify({ MessageID: "m", SubmittedAt: new Date().toISOString() }), { status: 200 })
}) as unknown as typeof fetch

await createEmail({ driver: postmark({ token: "t", fetch: f }), defaults: { from: "Acme <hi@acme.com>" } })
  .send({ to: "a@x.com", subject: "s", text: "t", tags: [{ name: "campaign", value: "welcome-2026" }] })

console.log("Postmark payload Tag/Metadata:", JSON.stringify({ Tag: body.Tag, Metadata: body.Metadata }))
console.log("the value 'welcome-2026' appears anywhere in the payload?", JSON.stringify(body).includes("welcome-2026"))

Observed output:

Postmark payload Tag/Metadata: {"Tag":"campaign"}
the value 'welcome-2026' appears anywhere in the payload? false

Why it matters

EmailTag is a name/value pair, and every other driver sends both halves — resend emits tags: [{ name, value }], ses emits EmailTags: [{ Name, Value }]. Only postmark discards the value, and only for the first tag, so a caller who tags every message { name: "campaign", value: "welcome-2026" } gets Postmark analytics grouped under the literal string campaign with the campaign identity gone. Add a second tag and the second one's value survives while the first one's still does not, which makes the behaviour hard to notice from the outside.

Postmark's Tag field is a single free-form string, so there is room to carry both.

Where

  • src/drivers/postmark.ts:179-187 — toPayload() tag handling
  • compare src/drivers/resend.ts:154 and src/drivers/ses.ts:148-150

Suggested fix

Keep Tag as the primary grouping key but stop losing the value: either set Tag to the first tag's value when there is one (value is what identifies the campaign; name is the dimension), or keep Tag = name and additionally write Metadata[name] = value for every tag including the first — one line, and it makes the existing comment true.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions