Skip to content

fix(mailgun): preserve duplicate custom headers - #147

Open
faisalahammad wants to merge 1 commit into
wpexpertsio:old/devfrom
faisalahammad:fix/74-mailgun-multiple-headers
Open

fix(mailgun): preserve duplicate custom headers#147
faisalahammad wants to merge 1 commit into
wpexpertsio:old/devfrom
faisalahammad:fix/74-mailgun-multiple-headers

Conversation

@faisalahammad

Copy link
Copy Markdown

Summary

Preserves multiple headers with the same key when sending through the Mailgun transport. RFC 5322 section 3.6 permits repeated optional-field headers and Mailgun documents h:X-Mailgun-Tag as accepting an array of strings, but PostmanMailgunMailEngine was flat-assigning $mailgunMessage["h:" . $name] on every iteration so only the last value survived.

Fixes #74

Changes

Postman/Postman-Mail/PostmanMailgunMailEngine.php

Before:

private function addHeader( $name, $value, $deprecated = "" ) {
    if ( $value && ! empty( $value ) ) {
        $this->mailgunMessage["h:" . $name] = preg_replace("/.*:\s?/", "", $value);
    }
}

After:

private function addHeader( $name, $value, $append = false ) {
    if ( $value && ! empty( $value ) ) {
        $clean = preg_replace( "/.*:\s?/", "", $value );
        $key   = "h:" . $name;

        if ( $append && isset( $this->mailgunMessage[ $key ] ) ) {
            $existing = $this->mailgunMessage[ $key ];
            if ( ! is_array( $existing ) ) {
                $existing = array( $existing );
            }
            $this->mailgunMessage[ $key ] = array_merge( $existing, array( $clean ) );
        } else {
            $this->mailgunMessage[ $key ] = $clean;
        }
    }
}

Why: the third arg was a dead $deprecated placeholder. Rename it to $append=false, and when the header loop at line 197 sets it to true and the same key is already present, append instead of overwrite. The first occurrence still hits the else branch, so single-value behaviour is byte-identical.

Postman/Postman-Mail/Services/MailGun/Handler.php

Before: the multipart branch only activated when attachment was set.
After: also activates when any content value is an array.
Why: when headers are accumulated as arrays, wp_remote_post body serialisation would have produced h:X-Mailgun-Tag[0] and [1] query keys, which Mailgun would have parsed as literal header names instead of repeated tags. Routing duplicate header tags through the existing multipart emitter keeps the wire shape Mailgun expects.

Repro

add_action("init", function () {
    if ( ! isset( $_GET["mailgun_tags_test"] ) ) return;
    wp_mail(
        "you@example.com",
        "Multi-tag test",
        "Body",
        array(
            "X-Mailgun-Tag: test-category-account",
            "X-Mailgun-Tag: test-account-reset-password",
        )
    );
});

Before: Mailgun dashboard log shows one tag. After: both tags appear.

Testing

Test 1: duplicate tags delivered

  1. Configure Post SMTP for the Mailgun transport.
  2. Trigger the snippet above.
  3. Open the Mailgun dashboard log for the message.
    Result: both test-category-account and test-account-reset-password are listed as tags. ✓

Test 2: single tag still works

  1. Replace both lines with one X-Mailgun-Tag: single-tag.
  2. Trigger the snippet.
    Result: one tag. ✓ (No regression for single-value behaviour.)

Test 3: attachment still works

  1. Send a normal Mailgun email with a file attachment and no custom headers.
  2. Confirm the message delivers and the attachment arrives intact.
    Result: byte-identical to before. ✓ (No regression in the multipart path.)

Notes

  • Other API engines (SendGrid, SMTP2Go, MailerSend, Mailjet, Sendinblue, Sendpulse, Postmark, SparkPost) have the same flat-assign pattern. They are out of scope for this PR by maintainer decision. A follow-up could tackle them with the same one-liner.
  • No composer / phpunit / phpcs in this repo, so verification was an off-tree reproduction harness plus the manual scenarios above.

When a wp_mail() call passes two or more headers with the same name
(for example several X-Mailgun-Tag lines for Mailgun tag routing),
only the last value reached the Mailgun API before this change.
RFC 5322 section 3.6 permits repeated optional-field headers and
Mailgun's API documents h:X-Mailgun-Tag as supporting an array of
strings.

- PostmanMailgunMailEngine::addHeader third argument was an unused
  $deprecated flag. Rename to $append=false and use it: when the
  loop reports $append=true and an h:<name> key is already set,
  accumulate the new value into an array instead of overwriting.
  The first occurrence still assigns a scalar, so single-value use
  is byte-identical to before.
- PostmanMailGun\Handler::get_headers enters the multipart branch
  whenever any content value is an array, not only when an
  attachment is present. This sends duplicate header tags as
  repeated form fields (which Mailgun accepts) instead of as
  bracketed PHP query string keys via http_build_query.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support multiple headers with same key

1 participant