Struggling with multipart/form-data to send email

Has anyone been able to use the multipart/form-data schema to send emails in PHP ? I’ve been trying for some time now to get it right, but I just can’t get it to work. The JSON schema works just fine, but due to the nature of the business of our users, bigger emails also needs to be supported.

This is currently what’s holding back our migration from v2 to v3.

I think the biggest hurdle is getting the actual message content in the request, the attachments are, from what I think, done correctly. But the message data (to, from, body, …) is where I think I’m doing things wrong.

I have tried the following :

Using a library to build multipart :
https://docs.php-http.org/en/latest/components/multipart-stream-builder.html

$fields = [
            'subject' => $subject,
            'to' => [
                'to' => ['name' => $recipientName, 'email' => $recipientEmailAddress],
            ],
            'body' => $body,
        ];

        if ($ccAddresses) {
            $fields['cc'] = $this->addAddresses($ccAddresses);
        }
        if ($bccAddresses) {
            $fields['bcc'] = $this->addAddresses($bccAddresses);
        }

        $url = 'https://api.us.nylas.com/v3/grants/'.$grantId.'/messages/send';

        $streamFactory = Psr17FactoryDiscovery::findStreamFactory();
        $builder = new MultipartStreamBuilder($streamFactory);
        $builder->addData(json_encode($fields));

        foreach ($attachments as $attachment) {
            $builder->addResource($attachment['filename'], $attachment['content'], ['filename' => $attachment['filename']]);
        }

        $multipartStream = $builder->build();
        $boundary = $builder->getBoundary();

        $request = new Request('POST', $url, [
                'Authorization' => 'Bearer ' . $this->getApiKey(),
                'Content-Type' => 'multipart/form-data; boundary="'.$boundary.'"'
            ],
            $multipartStream->getContents()
        );

        /** @var \GuzzleHttp\Psr7\Response $response */
        $response = Psr18ClientDiscovery::find()->sendRequest($request);

A cURL approach :

        $fields = [
            'subject' => $subject,
            'to' => [
                'to' => ['name' => $recipientName, 'email' => $recipientEmailAddress],
            ],
            'body' => $body,
        ];

        if ($ccAddresses) {
            $fields['cc'] = $this->addAddresses($ccAddresses);
        }
        if ($bccAddresses) {
            $fields['bcc'] = $this->addAddresses($bccAddresses);
        }

        $url = 'https://api.us.nylas.com/v3/grants/'.$grantId.'/messages/send';

        // curl

        $curl = curl_init();

        $boundary = uniqid();
        $delimiter = '-------------' . $boundary;

        $post_data = $this->buildDataFiles($boundary, $fields, $attachments);

        curl_setopt_array($curl, array(
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => 1,
            CURLOPT_MAXREDIRS => 10,
            CURLOPT_TIMEOUT => 30,
            //CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
            CURLOPT_CUSTOMREQUEST => "POST",
            CURLOPT_POST => 1,
            CURLOPT_POSTFIELDS => $post_data,
            CURLOPT_HTTPHEADER => array(
                //"Authorization: Bearer $TOKEN",
                "Content-Type: multipart/form-data; boundary=" . $delimiter,
                "Content-Length: " . strlen($post_data),
                'authorization' => 'Bearer ' . $this->getApiKey(),
            ),
        ));

        $errorMessage = null;

            $response = curl_exec($curl);

            $info = curl_getinfo($curl);

            $err = curl_error($curl);

            curl_close($curl);

We’re currently on Symfony 3.4 and I tried using some of Symfony’s FormDataPArt as well, but no luck :

Can anyone help guide me to get it right ?

hey Kenny! Thanks for working to upgrade your app to v3.

Here’s some PHP code that I found worked to send a multipart message with an attachment > 3mb:

<?php

function sendEmailWithAttachments($token, $grantId, $subject, $body, $recipientName, $recipientEmail, $attachments) {
    $curl = curl_init();

    $headers = [
        "Authorization: Bearer $token",
        "Content-Type: multipart/form-data"
    ];

    // Dynamic generation of attachment field names
    $postFields = [
        'message' => json_encode([
            'subject' => $subject,
            'body' => $body,
            'to' => [
                [
                    'name' => $recipientName,
                    'email' => $recipientEmail
                ]
            ]
        ])
    ];

    // Adding the attachments dynamically
    foreach ($attachments as $attachment) {
        // Create a unique identifier for each attachment
        $identifier = uniqid('attachment_', true);
        $postFields[$identifier] = new CURLFile($attachment);
    }

    curl_setopt_array($curl, [
        CURLOPT_URL => "https://api.us.nylas.com/v3/grants/$grantId/messages/send",
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_VERBOSE => true,
        CURLOPT_CUSTOMREQUEST => "POST",
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_POSTFIELDS => $postFields
    ]);

    $response = curl_exec($curl);

    if (curl_errno($curl)) {
        echo 'Error:' . curl_error($curl);
    }

    curl_close($curl);

    return $response;
}

// Example usage
$token = 'TOKEN-REDACTED'; // Replace with your actual token
$grantId = 'your-grant-id-here'; // Replace with your grant ID
$subject = 'Happy Birthday from Nylas!';
$body = "Wishing you the happiest of birthdays. \n Fondly, \n -Nylas";
$recipientName = 'Jacob Doe';
$recipientEmail = 'jacob.doe@example.com';
$attachments = [
    '/path/to/file',
    '/path/to/file2'
];

$response = sendEmailWithAttachments($token, $grantId, $subject, $body, $recipientName, $recipientEmail, $attachments);

echo $response;
?>

The same code should work on the /drafts endpoint if you want to save a draft first before you send the message.

Your code looks close, but at the very least the “to” field is formatted incorrectly (shouldn’t be nested twice) . It’s also not necessary to set the Content-Type and Content-Length headers manually, as cURL can do that automatically and make sure they are correct.

Let me know if this works for you!

Thanks, I was able to send the email with this! The issue I’m having now is the attachments are sent, but I can’t open the file, even though it’s a PDF, it complain the mimetype is text/plain.

The files are stored in the database, so we have the file content, not a path/url. I tried using tmp files, it sends the file, but with wrong mimetype. The size and mimetype shown in ‘properties’ of the file are correct.

        // Adding the attachments dynamically
        foreach ($attachments as $attachment) {
            // Create a unique identifier for each attachment
            $identifier = uniqid('attachment_', true);
            $temp_file = tempnam(sys_get_temp_dir(), 'tdb_send_attachment');
            file_put_contents($temp_file , $attachment['content']);

            $postFields[$identifier] = new \CURLFile($temp_file, $attachment['content_type'], $attachment['filename']);
        }

Receive email message content :

Content-Type: multipart/related; boundary="00000000000054187a0623da8c72"

--00000000000054187a0623da8c72
Content-Type: multipart/alternative; boundary="0000000000005418780623da8c71"

--0000000000005418780623da8c71
Content-Type: text/plain; charset="UTF-8"

test

--0000000000005418780623da8c71
Content-Type: text/html; charset="UTF-8"

<p>test</p>

--0000000000005418780623da8c71--
--00000000000054187a0623da8c72
Content-Type: application/pdf; name="Company Alfred E. Neuman Resume #2.pdf"
Content-Disposition: inline; filename="Company Alfred E. Neuman Resume #2.pdf"
Content-Transfer-Encoding: base64
X-Attachment-Id: 380fdfa4e00d96f3_0.1


--00000000000054187a0623da8c72--

Ok the attachments were an issue on our side, it was base64 encoded, which it shouldn’t.

1 Like

Great to hear it Kenny!! \o/