Garrett St. John

I am a web developer and partner at Bold. This is where I share my thoughts, discoveries and other random bits.

Extracting Attachments From Emails With PHP

February 8, 2011

Yesterday I wrote about how to read emails with PHP, but I want to dig a bit deeper and discuss another part of the My Slow Low project that needed tackling: Extracting attachments from emails with PHP. For the purposes of this post, I will be specifically discussing file attachments, not HTML inline attached files.

This project was executed with CodeIgniter 2.0 so there are a few libraries and helper functions used that will not be available in other frameworks or with straight PHP, however, their functionality can be pretty easily replicated.

function email_pull() {
	// load the Email_reader library from previous post
	$this->load->library('email_reader');

	// load the meals_model to store meal information
	$this->load->model('meals_model');

	// this method is run on a cronjob and should process all emails in the inbox
	while (1) {
		// get an email
		$email = $this->email_reader->get();

		// if there are no emails, jump out
		if (count($email) <= 0) {
			break;
		}

		$attachments = array();
		// check for attachments
		if (isset($email['structure']->parts) && count($email['structure']->parts)) {
			// loop through all attachments
			for ($i = 0; $i < count($email['structure']->parts); $i++) {
				// set up an empty attachment
				$attachments[$i] = array(
					'is_attachment' => FALSE,
					'filename'      => '',
					'name'          => '',
					'attachment'    => ''
				);

				// if this attachment has idfparameters, then proceed
				if ($email['structure']->parts[$i]->ifdparameters) {
					foreach ($email['structure']->parts[$i]->dparameters as $object) {
						// if this attachment is a file, mark the attachment and filename
						if (strtolower($object->attribute) == 'filename') {
							$attachments[$i]['is_attachment'] = TRUE;
							$attachments[$i]['filename']      = $object->value;
						}
					}
				}

				// if this attachment has ifparameters, then proceed as above
				if ($email['structure']->parts[$i]->ifparameters) {
					foreach ($email['structure']->parts[$i]->parameters as $object) {
						if (strtolower($object->attribute) == 'name') {
							$attachments[$i]['is_attachment'] = TRUE;
							$attachments[$i]['name']          = $object->value;
						}
					}
				}

				// if we found a valid attachment for this 'part' of the email, process the attachment
				if ($attachments[$i]['is_attachment']) {
					// get the content of the attachment
					$attachments[$i]['attachment'] = imap_fetchbody($this->email_reader->conn, $email['index'], $i+1);

					// check if this is base64 encoding
					if ($email['structure']->parts[$i]->encoding == 3) { // 3 = BASE64
						$attachments[$i]['attachment'] = base64_decode($attachments[$i]['attachment']);
					}
					// otherwise, check if this is "quoted-printable" format
					elseif ($email['structure']->parts[$i]->encoding == 4) { // 4 = QUOTED-PRINTABLE
						$attachments[$i]['attachment'] = quoted_printable_decode($attachments[$i]['attachment']);
					}
				}
			}
		}

		// for My Slow Low, check if I found an image attachment
		$found_img = FALSE;
		foreach ($attachments as $a) {
			if ($a['is_attachment'] == 1) {
				// get information on the file
				$finfo = pathinfo($a['filename']);

				// check if the file is a jpg, png, or gif
				if (preg_match('/(jpg|gif|png)/i', $finfo['extension'], $n)) {
					$found_img = TRUE;
					// process the image (save, resize, crop, etc.)
					$fname = $this->_process_img($a['attachment'], $n[1]);

					break;
				}
			}
		}

		// if there was no image, move the email to the Rejected folder on the server
		if ( ! $found_img) {
			$this->email_reader->move($email['index'], 'INBOX.Rejected');
			continue;
		}

		// get content from the email that I want to store
		$addr   = $email['header']->from[0]->mailbox."@".$email['header']->from[0]->host;
		$sender = $email['header']->from[0]->mailbox;
		$text   = ( ! empty($email['header']->subject) ? $email['header']->subject : '');

		// move the email to Processed folder on the server
		$this->email_reader->move($email['index'], 'INBOX.Processed');

		// add the data to the database
		$this->meals_model->add(array(
			'username'    => $sender,
			'email'       => $addr,
			'photo'       => $fname,
			'description' => ($text == '' ? NULL : $text)
		));

		// don't slam the server
		sleep(1);
	}

	// close the connection to the IMAP server
	$this->email_reader->close();
}

I tried to comment the code above as best as possible to convey what it is I am doing with the information. This code is a method within a Controller, but I’ve only included the attachment extraction method of the class for this post.

For more information and to credit those I’ve learned from, please check out David Walsh’s post on Retriev[ing] Your Gmail Emails with PHP and IMAP as well as Chris Hope’s post on Extracting attachments from an email message using PHP IMAP functions.

  • Sajid Shah

    This is a wonderful tutorial, exactly what I am looking for… Everything works perfect, I am using it with Codeigniter. If you could please upload the function “_process_img”, it will really complete my code…

    $this->_process_img($a['attachment'], $n[1]);

    GREAT WORK

  • Garrett St. John

    Hey Sajid,

    Thanks for the comment and glad the code is helping.

    The contents of the method will largely depend on what you want to do with the image based on your needs. You should check out the Image Functions in the PHP docs for what is available to you. As far as actually implementing them, that is out of scope for this post, but I would recommend starting with an existing library like the Intervention Image Class.

    Hope this helps!

  • Anonymous

    This function and the class on the previous post are AWESOME! Together, these two make a terrific combination :)

    I just had to make a few modifications and it works like a charm to process documents sent via email.

    Thanks for sharing!

  • Garrett St. John

    Thanks, glad to help!