PHP: SendGrid mail attachments from S3 server - php

I need to be able to send one, maybe more, files stored on an Amazon S3 server as attachments in an email created using SendGrid.
The problem I have is that I'm not a web dev expert and the sparse PHP examples I can find are not helping me much.
Do I need to download the files from the S3 server to the local /tmp directory and add them as attachments that way, or can I pass the body of the file from the FileController and insert it as an attachment that way?
I'm not really sure where to start, but here's what I've done so far:
$attachments = array();
// Process the attachment_ids
foreach($attachment_ids as $attachment_id) {
// Get the file if it is attached to the Activity
if (in_array($attachment_id, $activity_file_ids)) {
$file = File::find($attachment_id);
$fileController = new FileController($this->_app);
$fileObject = $fileController->getFile($attachment_id);
error_log(print_r($fileObject, true));
$attachment = array();
$attachment['content'] = $fileObject;
$attachment['type'] = $fileController->mime_content_type($file->file_ext);
$attachment['name'] = explode(".", $file->filename, 2)[0];
$attachment['filename'] = $file->filename;
$attachment['disposition'] = "inline";
$attachment['content_id'] = '';
}
}
My next step would be to push the $attachment array to the $attachments array. Once $attachments is complete, iterate through it and add each $attachment to the SendGrid e-mail object (the e-mail is working fine without attachments, btw.)
Problem is, I'm not sure I'm going down the right road with this or if there's a much shorter and neater (and working) way of doing it?
FileController->getFile() essentially does this:
$file = $this->_s3->getObject(array(
'Bucket' => $bucket,
'Key' => $filename,
));
return $file['Body'];
Any help (especially code examples) would be greatly appreciated!

Okay, I've got a working solution to this now - here's the code:
// Process the attachment_ids
foreach($attachment_ids as $attachment_id) {
// Get the file if it is attached to the Activity
if (in_array($attachment_id, $activity_file_ids)) {
// Get the file record
$file = File::find($attachment_id);
// Get an instance of FileController
$fileController = new FileController($this->_app);
// Set up the Attachment object
$attachment = new \SendGrid\Attachment();
$attachment->setContent(base64_encode($fileController->getFile($attachment_id)));
$attachment->setType($fileController->mime_content_type($file->file_ext));
$attachment->setFilename($file->filename);
$attachment->setDisposition("attachment");
$attachment->setContentId($file->file_desc);
// Add the attachment to the mail
$mail->addAttachment($attachment);
}
}
Don't know if it will help anybody else, but there it is. The solution was to get the file from the S3 server and pass base64_encode($file['Body']) to the setContent function of an instantiated Attachment object, along with setting a few other fields for it too.

Related

Embed Image using php mailer

I'm currently creatting a mailer and embed image. So when I set a specific directory ( ./upload/tmp_logo_company_upload/company_logo_resized.jpg ) for image it successfully send to my email and it shows the image that i embed.
But I want is to get all the image in the upload directory so whenever I run the script all images from upload folder will send to me. Can someone help me out for this, Below you will see my code. Thanks.
if( !defined('sugarEntry') ) define( 'sugarEntry',true );
// Set required classes
require_once('include/entryPoint.php');
// Get access to globals
global $db, $current_user, $timedate;
$arRecipientsId = array();
$objDefaultMail = new Email();
$objDefault = $objDefaultMail->getSystemDefaultEmail();
$objMail = new SugarPHPMailer();
$objMail->setMailerForSystem();
$objMail->From = $objDefault['email'];
$objMail->FromName = $objDefault['name'];
$objMail->Subject = "";
// Set the Location of photo
$strLocation = './upload/tmp_logo_company_upload/company_logo_resized.jpg';
// Add image
$objMail->AddEmbeddedImage($strLocation, 'company_logo_resized.jpg');
//
$objMail->Body = "Here. <img src='cid:company_logo_resized.jpg'>";
// Send as HTML
$objMail->IsHTML(true);
// Clear previous email data
$objMail->ClearAllRecipients();
$objMail->ClearReplyTos();
$objMail->prepForOutbound();
// Store recipients
$arRecipientsId[] = "1"; //crmonline
// Add as BCC
$objMail->AddBCC('email#gmail.com');
// Mail sent?
if( $objMail->send() ) {
// echo "Email Has Been Sent";
$GLOBALS['log']->fatal("WORKING");
}
// }
There's no need to call ClearAllRecipients or ClearReplyTos - the things those clear are already empty because you have a new instance.
To iterate over all the files in a folder, use a DirectoryIterator - that will get all the filenames you need, so you just need to call addEmbeddedImage for each and add <img src="cid:xxx"> tags for each image you want to embed.
$dir = './upload/tmp_logo_company_upload';
$cidn = 0;
foreach (new DirectoryIterator($dir) as $fileInfo) {
if($fileInfo->isDot() or $fileInfo->isDir()) continue;
$objMail->addEmbeddedImage($dir . DIRECTORY_SEPARATOR . $fileInfo->getFilename(), "img_$cidn");
$mailObj->Body .= "<img src=\"cid:img_$cidn\">";
++$cidn;
}
That said, this is an inefficient way of using images in email - it is much better to use HTTP links to the images - that way they are only loaded when the recipients ask for them, rather than sending them regardless.
Its pretty simple , you are missing complete image public path
change this to following
$objMail->Body = "Here. <img src='cid:company_logo_resized.jpg'>";
To (Image Public Path including http)
$objMail->Body = "Here. <img src='http://example.com/publicppath /company_logo_resized.jpg'>";
Adding code for Reading the Image from dir
//path to the dir
$dir = '/upload';
$files = scandir($dir);
foreach ($files as $image) {
$all_img .= " <img src='http://example.com/imagepath/".$image."'><br>";
}
//for embeding image
foreach ($files as $image) {
$im = file_get_contents($image);
$em_img .="<img src='data:image/jpg;base64,".base64_encode($im)."'> <br>"
}
//via public path
$objMail->Body = "Here ".$all_img;
//or embed sending
$objMail->Body = "Here ".$em_img;
it should work

FD Table is full error while sending email using SES

I'm trying to send 2K emails to my customers. I'm using Amazon SES to send the email using this library. When I try to send the email with the attachment, I'm getting SimpleEmailService::sendEmail(): 35 Process open FD table is full error. I just followed the instruction given in the readme file. And changed credentials only. Any help will be appreciated... :)
Edit
Almost 900 emails with the attachment sent successfully. But after that, it starts throwing the error.
The attachment is of type PDF.
Edit 2
The service team says that you are not closing the file descriptor after sending the mails. You need to close the file descriptor after the job is done. That way it wont exhaust the limit set on the number of FDs assigned
Here is the code I'm trying.
$mails = [
'customer#email.com'
...
...
...
];
$ses = new SimpleEmailService('XXXXXXX', 'XXXXXXX');
$ses->enableVerifyPeer(false);
$m = new SimpleEmailServiceMessage();
foreach ($mails as $email) {
try {
$m->setFrom('From <example#email.com>');
$m->setSubject('Subject');
$m->setMessageFromString('','<p>This is the test email.</p>');
$m->addTo($email);
$atch = path to pdf;
$mime_type = #mime_content_type($atch);
$tmp = str_replace('\\','/', $atch);
$file_name = basename($tmp);
$m->addAttachmentFromFile($file_name, $atch, $mime_type);
$response = $ses->sendEmail($m,false,true);
$m->clearRecipients();
$m->attachments = [];
} catch (Exception $ex) {
echo $ex->getMessage();
}
}
Solved
Just added $ses->setBulkMode(true); after $ses->enableVerifyPeer(false);. ..:)

sugarCRM add document from script

I am trying to add Document to Sugar object (client) from PHP script. I have a directory of files (on the same server where sugarCRM is installed) and xls with sugar objec ID and filename). PHP Script should add correct filename to specific sugar object (identified with ID). I can read XLS this is no problem, I can also get instance of sugar object (retrieve by ID), but I have no idea how can I assign the file to sugar. I was trying with Document, and upload_file.php, but those seem to be usable with uploading single file with html Form.
How can I automate this task, copy files with correct filename to cache\upload and create Document related to my Customer from PHP Script? I would prefer not to use SOAP if it's not necesarry...
Edit:
I was able to save document and revision, but something is wrong, and file can't be downloaded from browser ("incorrect call to file")
My Code so far is:
require_once('include/upload_file.php');
$upload_file = new UploadFile('uploadfile');
$document->filename = 'robots.txt';
$document->document_name = 'robots.txt';
$document->save();
$contents = file_get_contents ($document->filename);
$revision = new DocumentRevision;
$revision->document_id = $document->id;
$revision->file = base64_encode($contents);
$revision->filename = $document->filename;
$revision->revision = 1;
$revision->doc_type = 'Sugar';
$revision->file_mime_type = 'text/plain';
$revision->save();
$document->revision_id = $revision->id;
$document->save();
$destination = clean_path($upload_file->get_upload_path($document->id));
$fp = sugar_fopen($destination, 'wb');
if( !fwrite($fp, $contents) ){
die("ERROR: can't save file to $destination");
}
fclose($fp);
WORKS! I Hope this will help someone
I have corrected 3 lines from code abowe:
//$document->revision_id = $revision->id;
//$document->save();
$destination = clean_path($upload_file->get_upload_path($revision->id));

how to download mails attachment to a specific folder using IMAP and php

i am developing a site in which users can mail tickets and attach any type of files to a specific mail id. I need to add the mail subject, content and attachment to the database. I am doing this using cron. Except the attachments every thing works perfect. I have seen some post which create download links. Since i am using cron i can't do it manually.
$hostname = '{xxxx.net:143/novalidate-cert}INBOX';
$username = 'yyy#xxxx.net';
$password = 'zzzz';
/* try to connect */
$inbox = imap_open($hostname,$username,$password) or die('Cannot connect to : ' . imap_last_error());
$emails = imap_search($inbox,'ALL');
if($emails) {
$output = '';
rsort($emails);
foreach($emails as $email_number) {
$structure = imap_fetchstructure($inbox, $email_number);
$name = $structure->parts[1]->dparameters[0]->value; // name of the file
$type = $structure->parts[1]->type; //type of the file
}}
I am able to get type and name of the files but don't know how to proceed further
Any one please help me. thank you...
To save attachments as files, you need to parse the structure of the message and take out all parts that are attachments on it's own (content disposition). You should wrap that into classes of their own so you have an easy access and you can handle errors more easily over time, email parsing can be fragile:
$savedir = __DIR__ . '/imap-dump/';
$inbox = new IMAPMailbox($hostname, $username, $password);
$emails = $inbox->search('ALL');
if ($emails) {
rsort($emails);
foreach ($emails as $email) {
foreach ($email->getAttachments() as $attachment) {
$savepath = $savedir . $attachment->getFilename();
file_put_contents($savepath, $attachment);
}
}
}
The code of these classes is more or less wrapping the imap_... functions, but for the attachment classes, it's doing the parsing of the structures as well. You find the code on github. Hope this is helpful.
Although using PHP + Cron and a standard mail server might work, the amount of work needed to handle all the edge cases, error reporting, etc might not be worth the time. Although I haven't used it, Postmark Inbound seems like an incredible (paid) service that will eliminate most of the headache of processing email via the PHP imap api.
If you want to try to handle everything via PHP, you might want to check this resource out.
In case if you want to download attachments as zip
$zip = new ZipArchive();
$tmp_file = tempnam('.', '');
$zip->open($tmp_file, ZipArchive::CREATE);
$mailbox = $connection->getMailbox('INBOX');
foreach ($mailbox->getMessage() as $message) {
$attachments = $message->getAttachments();
foreach ($attachments as $attachment) {
$zip->addFromString($attachment->getFilename(), $attachment->getDecodedContent());
}
}
$zip->close();
# send the file to the browser as a download
header('Content-disposition: attachment; filename=download.zip');
header('Content-type: application/zip');
readfile($tmp_file);
This code uses library hosted on GitHub. Hope this is helpful.

PHP Foreach and jQuery

I am working on a piece of code that I am wanting to "spice" up with jQuery but I can't think of a way to actually make it work. I am sure its simple, I just need a little advice to get me going.
I am wanting to create a piece of code that makes an Ajax request out to start a big loop that will download files and then upload them to an S3 bucket of mine. The place where I am stuck is I am wanting to send back a request back to the browser everytime a file is uploaded and output a string of text to the screen upon completion.
I don't have any of the frontend code working... just trying to get my head wrapped around the logic first... any ideas?
PHP Backend Code:
<?php
public function photos($city) {
if(isset($city))
$this->city_name = "{$city}";
// grab data array from Dropbox folder
$postcard_assets = $this->conn->getPostcardDirContent("{$this->city_name}", "Photos", TRUE);
$data = array();
foreach($postcard_assets['contents'] as $asset) {
//only grab contents in root folder... do not traverse into sub folders && make sure the folder is not empty
if(!$asset['is_dir'] && $asset['bytes'] > 0) {
// get information on file
$file = pathinfo($asset['path']);
// download file from Dropbox
$original_file = $this->conn->downloadFile(str_replace(" ", "%20", $asset['path']));
// create file name
$file_name = $this->cleanFileName($file['basename']);
// write photo to TMP_DIR ("/tmp/photos/") for manipulation
$fh = fopen(self::TMP_DIR . $file_name, 'w');
fwrite($fh, $original_file);
fclose($fh);
// Resize photo
$this->resize_photo($file_name);
// hash file name
$raw_file = sha1($file_name);
// create S3 hashed name
$s3_file_name = "1_{$raw_file}.{$file['extension']}";
// Upload manipulated file to S3
$this->s3->putObject($s3_file_name, file_get_contents(self::TMP_DIR . $file_name), $this->photo_s3_bucket, 'public-read');
// check to see if file exists in S3 bucket
$s3_check = $this->s3->getObjectInfo($s3_file_name, $this->photo_s3_bucket);
// if the file uploaded successully to S3, load into DB
if($s3_check['content-length'] > 0) {
$data['src'] = $s3_file_name;
$data['width'] = $this->width;
$data['height'] = $this->height;
Photo::create_postcard_photo($data, "{$this->city_name}");
// Now that the photo has been uploaded to S3 and saved in the DB, remove local file for cleanup
unlink(self::TMP_DIR . $file_name);
echo "{$file_name} uploaded to S3 and resized!<br />";
}
}
}
// after loop is complete, kill script or nasty PHP header warnings will appear
exit();
}
?>
The main problem is that with PHP, the output is buffered so it won't return a line at a time. You can try and force the flush but it's not always reliable.
You could add an entry to the DB for each file that is exchanged and create a seperate API to get the details of what has completed.
Generally, Jquery will wait till the request has finished before it allows you to manipulate data from a HTTP request.

Categories