Skip to content

Multipart emails fail to send when using Nylas::FileUtils.handle_message_payload #525

@duncanmorrissey

Description

@duncanmorrissey

Describe the bug

Nylas::HttpClient#build_request assumes the multipart flag will be
present under the string key "multipart":

if !payload.nil? && !payload["multipart"]
  payload = payload.to_json
  headers["Content-type"] = "application/json"
elsif !payload.nil? && payload["multipart"]
  payload.delete("multipart")
end

However, Nylas::FileUtils.handle_message_payload transforms all of its keys into symbols, including :multipart.

def self.handle_message_payload(request_body)
  payload = request_body.transform_keys(&:to_sym)
  # ...

This means HttpClient incorrectly sends multipart emails as application/json, causing them to error out.

To Reproduce
Some steps involved to reproduce the bug and any code samples you can share.

# reproduce_nylas_multipart_bug.rb
# ------------------------------------------------------------
# $ ruby reproduce_nylas_multipart_bug.rb
#
# Expected: Encoding::UndefinedConversionError raised
# Source: Nylas Ruby SDK ≤ 6.4.0 (multipart symbol key not respected)

require "nylas"
require "stringio"
require "json"

# ------ 1.  Fake “mailer” that builds the Nylas request body ------
class DemoNylasMailer
  FORM_SIZE = 3 * 1024 * 1024           # ← keeps code identical to SDK

  def initialize
    @message_body = "<h1>Hello</h1>"
  end

  # returns the Hash we finally pass to the SDK
  def request_body
    attachment = large_attachment               # >3 MB on purpose
    body = {
      body:          @message_body,
      from:          [{name: "Alice", email: "alice@example.com"}],
      to:            [{email: "bob@example.com"}],
      subject:       "Multipart‑symbol bug",
      tracking_options: {label: "demo", links: true, opens: true},
      attachments:   [attachment]
    }

    # SDK helper turns it into multipart/form‑data pieces
    payload, = Nylas::FileUtils.handle_message_payload(body)

    # ---------------------   BUG TRIGGER   -----------------------
    # the helper produced payload[:multipart] **(symbol key)**
    # HttpClient#build_request checks *string* "multipart",
    # so it thinks this is *NOT* multipart and JSON‑encodes the Hash
    # …which still contains binary values → Encoding error.
    payload
  end

  private

  def large_attachment
    io = StringIO.new("A" * (FORM_SIZE + 100_000))   # 3 MB+
    { filename: "big.csv",
      content_type: "text/csv",
      size: io.size,
      content: io }
  end
end

# ------ 2.  Tiny throw‑away client that only needs build_request ------
DummyClient = Class.new do
  include Nylas::HttpClient
  attr_accessor :api_server                     # HttpClient expects this ivar
end

client = DummyClient.new
client.api_server = "https://api.nylas.com"     # any string is fine

# ------ 3.  Execute build_request – watch it explode ---------------
mailer  = DemoNylasMailer.new
payload = mailer.request_body

# THIS IS application/json BECAUSE OF THE BUG
client.send(
  :build_request,
  method:  :post,
  path:    "/v3/grants/DUMMY/messages",
  payload: payload,
  timeout: 60,
  api_key: "DUMMY_KEY"
)

Expected behavior

Multipart emails should be sent as multipart

SDK Version:

6.4.0

Additional context

The below solves the problem:

def build_request(
  method:, path: nil, headers: {}, query: {}, payload: nil, timeout: nil, api_key: nil
)
  url = build_url(path, query)
  resulting_headers = default_headers.merge(headers).merge(auth_header(api_key))
  # new line:
  payload = payload.transform_keys(&:to_s) if payload.is_a?(Hash)
  if !payload.nil? && !payload["multipart"]
    payload = payload&.to_json
    resulting_headers["Content-type"] = "application/json"
  elsif !payload.nil? && payload["multipart"]
    payload.delete("multipart")
  end

  { method: method, url: url, payload: payload, headers: resulting_headers, timeout: timeout }
end

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions