0

Catch Up Quickly: Due to the nature of what I've most recently found. I have recreated this question in a different category because this does not seem to be a Mailgun issue. That question can be found here:

PHP 7.2 CURLFile Gives "Invalid Filename" Warning

I'm aware of the SDK and that Mailgun does not offer support outside of it, and that's why I'm trying to get some support here for a light-weight implementation I'm working on; it was actually already complete and working until I decided to extend it with attachments, and it's still working, just no attachments are being added to the email. I have reviewed a few previous questions, and I can't seem to get attachments to work based on the solutions offered.

Info

  • PHP 5.5 or Greater in Use (php 7.2 in my specific instance)
  • Attached files have been confirmed that they exist and are readable by PHP/Apache
  • php-curl and related php libraries are up-to-date
  • Sensitive variables in the code and output below have been modified (including the various email addresses/domains)
  • Emails deliver exactly as intended aside from the lack of attachments
  • This is a snippet from within an object, thus the references to $this. All variables are loading correctly.
  • Currently testing with a simple text email (not HTML).
  • The functionality is configured to send multiple attachments using attachment[] key approach, which is documented as working on other SO threads, BUT I have also tried using just one attachment approach and setting just the attachment key -- the result is the same.

Code Block

$curl = curl_init();

$log->AddStep('Mailgun', 'API send request starting...');
$postUrl = 'https://' . $this->host . self::API_URL_BASE . '/' . $this->domain . '/messages';

$curlOpts = array(
    CURLOPT_POST => 1,
    CURLOPT_URL => $postUrl,
    CURLOPT_TIMEOUT => 20,
    CURLOPT_RETURNTRANSFER => 1,
    CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
    CURLOPT_USERPWD => 'api:' . $this->apiKey
);

$postFields = array(
    'from' => $email->from,
    'to' => $email->to,
    'subject' => $email->subject
);

if (strlen($email->cc) > 0) {
    $postFields['cc'] = $email->cc;
}

if (strlen($email->bcc) > 0) {
    $postFields['bcc'] = $email->bcc;
}

if (strlen($email->html) > 0) {
    $postFields['html'] = $email->html;
} else {
    $postFields['text'] = $email->text;
}

if (count($email->attachments) > 0) {
    // Curl attachments for < PHP5.5 not supported
    if (function_exists('curl_file_create')) {
        $curlOpts[CURLOPT_SAFE_UPLOAD] = 1; // for < PHP 7
        //$curlOpts[CURLOPT_HTTPHEADER] = array('Content-Type: multipart/form-data');
        for ($i = 1; $i <= count($email->attachments); $i++) {
            $postFields['attachment[' . $i . ']'] = curl_file_create($email->attachments[$i - 1], "text/csv", basename($email->attachments[$i - 1]));
        }
    } else {
        \D3DevelForms\Models\Error::CreateAndSaveSystemError(
            $plugin, 
            \D3DevelForms\Common::ERROR_WARNING, 
            'PHP 5.5 or newer required for Mailgun Attachments', 
            \D3DevelForms\Models\Error::ERROR_CODE_API_MAILGUN_LOCAL_ERROR,
            'You are using an outdated version of PHP. Email attachments via Mailgun will be ignored.');
    }
}

$curlOpts[CURLOPT_POSTFIELDS] = $postFields;

$log->UpdateDebugLog('Mailgun API Options', $curlOpts);

curl_setopt_array($curl, $curlOpts);

$curl_response = curl_exec($curl);
$info = curl_getinfo($curl);

Curl Options ($curlOpts)

Array
(
    [47] => 1
    [10002] => https://api.mailgun.net/v3/devtester.devtest.com/messages
    [13] => 20
    [19913] => 1
    [107] => 1
    [10005] => api:APIKEY
    [-1] => 1
    [10015] => Array
        (
            [from] => Dev Tester <devtester@devtest.com>
            [to] => devemail@gmail.com
            [subject] => Form Summary
            [text] => My Text Content
            [attachment[1]] => CURLFile Object
                (
                    [name] => /var/www/path_to/my_file.csv
                    [mime] => text/csv
                    [postname] => my_file.csv
                )

        )

)

Curl Info Returned ($info)

Array
(
    [url] => https://api.mailgun.net/v3/devtester.devtest.com/messages
    [content_type] => application/json
    [http_code] => 200
    [header_size] => 388
    [request_size] => 312
    [filetime] => -1
    [ssl_verify_result] => 0
    [redirect_count] => 0
    [total_time] => 0.503718
    [namelookup_time] => 0.004273
    [connect_time] => 0.0932
    [pretransfer_time] => 0.279756
    [size_upload] => 1021
    [size_download] => 105
    [speed_download] => 208
    [speed_upload] => 2026
    [download_content_length] => 105
    [upload_content_length] => 1021
    [starttransfer_time] => 0.368725
    [redirect_time] => 0
    [redirect_url] => 
    [primary_ip] => Y.Y.Y.Y
    [certinfo] => Array
        (
        )

    [primary_port] => 443
    [local_ip] => X.X.X.X
    [local_port] => 38636
)

Mailgun API Response

{
    "id": "<AA.BB.CC@devtester.devtest.com>",
    "message": "Queued. Thank you."
}

Mailgun Log

{
    "tags": [],
    "envelope": {
        "sender": "devtester@devtest.com",
        "transport": "smtp",
        "targets": "devemail@gmail.com"
    },
    "storage": {
        "url": "https://sw.api.mailgun.net/v3/domains/devtester.devtest.com/messages/KK",
        "key": "KK"
    },
    "log-level": "info",
    "id": "II",
    "campaigns": [],
    "method": "http",
    "user-variables": {},
    "flags": {
        "is-routed": false,
        "is-authenticated": true,
        "is-system-test": false,
        "is-test-mode": false
    },
    "recipient-domain": "gmail.com",
    "timestamp": 1550085991.344005,
    "message": {
        "headers": {
            "to": "devemail@gmail.com",
            "message-id": "AA.BB.CC@devtester.devtest.com",
            "from": "Dev Tester <devtester@devtest.com>",
            "subject": "Form Summary"
        },
        "attachments": [],
        "size": 945
    },
    "recipient": "devemail@gmail.com",
    "event": "accepted"
}

I've been struggling with this for a couple days now with the goal of avoiding this question simply because of the anticipated resistance to not using the SDK, which I consider a last resort since this is all but working without needing to load an entire library.

Is there anything you guys can see that would prevent this code from sending the attachment via PHP + cURL?

Related Questions Reviewed:

Update: When testing with cURL from command line, it does work as intended including when I run it as an the apache process.

sudo -u apache curl -s --user 'api:APIKEY' \
    https://api.mailgun.net/v3/devtester.devtest.com/messages \
    -F from='Dev Tester <devtest@devtester.devtest.com>' \
    -F to='devtester@gmail.com' \
    -F subject='Hello' \
    -F text='Testing some Mailgun awesomness!' \
    -F attachment=@/var/www/path_to/my_file.csv
{
    "id": "<AA.BB.CC@devtester.devtest.com>",
    "message": "Queued. Thank you."
}

Update (Closing in on the Problem):

I am getting a PHP warning in the Apache logs, that appears as follows:

"PHP Warning: curl_setopt_array(): Invalid filename for key attachment[1]"

This is tricky because I have confirmed the following:

  • The file exists
  • The file is readable by Apache
  • The file path does not include any characters outside of letters, numbers, slashes and hyphens
  • Because the file is generated within the same thread, I have tried referencing a static file, but the result is the same.
Lawrence Johnson
  • 3,924
  • 2
  • 17
  • 30
  • Per their [docs](https://documentation.mailgun.com/en/latest/api-sending.html#sending), "Important: You must use multipart/form-data encoding when sending attachments.", so likely: `$curlOpts[CURLOPT_HTTPHEADER] = [ 'Content-Type: multipart/form-data' ]` – bishop Feb 13 '19 at 20:49
  • That header is automatically added when using `curl_file_create`, but for the purposes of sanity check, I have tried it and the result is the same. For reference, I updated the code block to where/how I have tested it. – Lawrence Johnson Feb 13 '19 at 20:51
  • Ok. Is the file path readable by the process executing this code? What happens if you use a key of `attachment` instead of `attachment[1]`? – bishop Feb 13 '19 at 21:10
  • Yes, the file path is readable (it is actually written by the same process earlier in the thread, but I confirmed it just in case). I have also tried using just `attachment` with a single file (since that's all I'm testing with at the moment anyway). Result is the same. – Lawrence Johnson Feb 13 '19 at 21:15
  • Hmm. If you use their API, with that file, does the attachment send? (I realize that's anathema. It's merely for differential diagnostics.) – bishop Feb 13 '19 at 21:17
  • If you mean their SDK, I haven't tried it yet. If I go as far as to implement it, I will likely scrap this approach and just stay with it. It's a last resort because the library is considerably larger than what is needed for this one feature, and this is meant for reusability, so I can't always guarantee that the environment it ends up in is optimized for loading it (php-fast for instance). – Lawrence Johnson Feb 13 '19 at 21:19
  • No offense to their devs, but only supporting their SDK is kind of backwards from the concept of an API which is all about not having proprietary platform requirements. I'm not a fan of their positioning on that. – Lawrence Johnson Feb 13 '19 at 21:21
  • 1
    Ok, well superficially the code looks fine to me, modulo being explicit on that header. If you don't want to even try out the SDK, then perhaps try it from the command line with `curl` straight up and then, once you get that working, diff the curl command line with the PHP $curlOpts set here. – bishop Feb 13 '19 at 21:25
  • Excellent idea. I gave it a shot, and it works. Is there maybe something with PHP cURL that I could be missing? I would imagine that all I need is `php-curl` library and any dependencies would get added. I'm going to try fiddling with the text and subject line parameters since those are the only things that were different in my cURL test – Lawrence Johnson Feb 13 '19 at 21:33
  • I found this PHP Warning "PHP Warning: curl_setopt_arr ay(): Invalid filename for key attachment[1]". Clearly this is the source of the problem, but I'm not sure what could cause this. The file is created, it can be read by the thread and even written (unlinked -- although i'm not unlinking it while testing). I am supplying a full path as opposed to a relative path as seen in some examples, but I have also seen examples with the full path. Google hasn't been much help with the error so far. – Lawrence Johnson Feb 13 '19 at 22:08
  • I also tried using a static file by hard-coding a filepath that exists prior to the thread to make sure there wasn't some type of delay in availability. Same warning appears. – Lawrence Johnson Feb 13 '19 at 22:09
  • From the looks of their code and API, they don't recognize array notation for attachments (i.e.) `attachment[1]`. What if you temporarily change the code to `$postFields['attachment'] = curl_file_create($email->attachments[$i - 1], "text/csv", basename($email->attachments[$i - 1]));` to try to send just 1? Their SDK builds a MIME message and sends multiple parts with the same name, `attachment`. I'm not sure the answer with that notation really worked. – drew010 Feb 13 '19 at 22:17
  • Thank you, @drew010. It's not possible to automatically build an attachment array, thus why you have to manually build the `[x]`. There are other recorded instances of this approach working; however, I have tried it with just `attachment` and only including one. This doesn't work. It's a lot of comments, so I'll update the OP, but I believe the answer lies in the filename path. – Lawrence Johnson Feb 13 '19 at 22:22
  • Thank you both for your contributions. I am closing and re-opening this question with a more accurate question/tags/description. – Lawrence Johnson Feb 13 '19 at 23:08
  • The problem is with `curl_setopt_array`. https://stackoverflow.com/a/54680979/1709104 – Lawrence Johnson Feb 13 '19 at 23:23

0 Answers0