Improving my Email sending Modal


#1

Hi there,

have a look at this:

Its a modal I use to send Emails in several places of my UI. I added the code I wrote so far below this text.

Now I want to extend its functionality (and share it of course) by adding and removing recipients and attachments.
The main problem is that its just an extended View at the moment, so not linked to any persistence - and thats whats needed if any adding/removing should take place.
So now I am figuring how to do this in a smart way and would appreciate your thoughts to point me in the right direction, so far I thought of:

Create a model and a view class for this modal. as any adding uses a form anyway, I could use Form->setModel sensibly.
The persistence used in the model class would be some session-based persistence (model only needs to be saved until Emails are sent), I think Persistence_Static does this? (Didn’t read into code yet).
This seems an ATK-like solution at first glance.
Do you approve or are there better ways to do what I want?

Thanks for your help
Philipp

How the modal is added in my normal code so far:

                $m = new \Pmg\ModalEmail();
		$m->addRecipient($group->getFirstEmail(), $group['contact_firstname'].' '.$group['contact_lastname'], $group['contact_firstname']);
		$m->modal_title = 'Anmelde-Erinnerung an Gästegruppe '.$group['name'].' schreiben';
		$m->subject = 'Für eure Tour fehlen noch Anmeldungen';
		$m->message = $email_reminder_html_template->render();
		$m->text_button = 'Anmelde-Erinnerung schreiben';
		$guest_form->layout->add($m);

ModalEmail Class:

<?php

namespace Pmg;

class ModalEmail extends \atk4\ui\Button {

	/*
	 * 2 dimensional associative array: 
	 * $recipients[email address] => ['name' => 'Recipient name', 'email' => 'Email Address', 'firstname' => 'Recipient Firstname']
	 * If 'firstname' is set, its tries to replace the {firstname} Tag inside the template with 'Recipient Firstname'
	 */
	public $recipients;
	
	//Title of the modal
	public $modal_title = '';
	
	//Email Subject
	public $subject = '';
	
	//Email message without Standard Header and Standard Footer
	public $message = '';
	
	//Text of the button that sends the Emails
	public $text_button = 'Email schreiben';

	//if true, only minimal button without text will be displayed
	public $mini_button = false;
	
	//Template for the standard HTML header
	public $t_header = TEMPLATE_PATH.'email/standard_header.html';
	
	//Template for the standard HTML Footer
	public $t_footer = TEMPLATE_PATH.'email/standard_footer.html';
	
	//All recipients that the email was sent to
	public $send_successes = [];
	
	//All recipients where email sending failed
	public $send_fails = [];

	//Array containing all attached file names
	public $attachments = [];

	//function that is called if at least one email was sent successfully
	public $onSuccessfulSend;

	//function that is called if at least one email failed to send
	public $onFailSend;
	

	/*
	 * adds a recipient. If same email address exists in recipient list,
	 * its overwritten. Ensures Email is only sent once to each email address.
	 */
	public function addRecipient($email_address, $name = null, $firstname = null) {
		$this->recipients[$email_address] = ['email' => $email_address, 'name' => $name, 'firstname' => $firstname];
	}

	/*
	 *	adds a file object to the attachment array.
	 */
	public function addAttachment(\Pmg\File $file) {
		$this->attachments[] = $file;
	}
	
	
	public function init($args = null) {
	
		parent::init();
		
		//Only Proceed if there is at least one recipient set
		if(count($this->recipients) > 0) {
				
			//Load Header and footer Templates
			$header = new \Pmg\Template();
			$header->load($this->t_header);
			$footer = new \Pmg\Template();
			$footer->load($this->t_footer);

			//VirtualPage contains Email Form
			$vp = new \atk4\ui\VirtualPage();
			$this->app->add($vp);
			$f = $vp->add('Form');
			$f->layout->inline = true;
			
			//Add recipient List
			$f->layout->add(['Text', 'Empfänger: ']);
			foreach($this->recipients as $r) {
				$f->layout->add(['Label', $r['name']])->addClass('pmg-email');
			}
			
			//correct singular/plural
			if(count($this->recipients) > 1) {
				$f->buttonSave->set('Emails versenden');
			}
			else {
				$f->buttonSave->set('Email versenden');
			}
			
			//Subject and Body
			$f->addField('subject');
			$f->model['subject'] = $this->subject;
			$f->addField('text_email', ['TextArea'])->addClass('textarea-summernote');
			$f->model['text_email'] = $this->message;

			//Attachments
			$f->layout->add(['Text', 'Dateianhänge: ']);
			foreach($this->attachments as $a) {
				$f->layout->add(['Label', $a->get('filename')])->addClass('pmg-email')->link(URL_BASE_PATH.$a->get('path').$a->get('filename'))->setAttr('target', '_blank');
			}
			
			//submit of form: send Emails
			$f->onSubmit(function () use ($f, $vp, $header, $footer) {
				$message_template = new \Pmg\Template();
				$message_template->loadTemplateFromString($f->model['text_email']);

				//Set all settings which are the same regardless of recipient
				$phpmailer = new \Pmg\PmgPHPMailer();
				$phpmailer->Subject = $f->model['subject'];
				//add Attachments
				foreach($this->attachments as $a) {
					$phpmailer->addAttachment($a->getFullFilePath());
				}
				//if email is sent to several recipients, keep SMTP connection open
				if(count($this->recipients) > 1) {
					$phpmailer->SMTPKeepAlive = true;
				}
				
				//single send for each recipient
				foreach($this->recipients as $r) {
					
					//custom salutation for each recipient
					if(isset($r['firstname'])) {
						$message_template->trySet('firstname', $r['firstname']);
					}

					//TODO: nl2br until WYSIWYG Editor is included
					$phpmailer->Body = $header->render().nl2br($message_template->render()).$footer->render();
					$phpmailer->AltBody = $phpmailer->html2text($phpmailer->Body);
					$phpmailer->addAddress($r['email'], $r['name']);
					
					//Send single Email, add Recipient to success or fail list
					if(!$phpmailer->send()) {
						$this->send_fails[] = $r['name'];
					}
					else {
						$this->send_successes[] = $r['name'];
						//add Email to IAMP
						$imapStream = imap_open(IMAP_PATH_SENT_MAIL, EMAIL_USERNAME, EMAIL_PASSWORD);
						$result = imap_append($imapStream, IMAP_PATH_SENT_MAIL, $phpmailer->getSentMIMEMessage());
						imap_close($imapStream);
					}

					//clear recipient after each Email
					$phpmailer->clearAddresses();
				}
				
				//create jsNotify which informs about success/fail
				$notify_message = '';
				$color = 'green';
				
				//create success info
				if(count($this->send_successes) > 0) {
					$notify_message = 'Email wurde zugestellt an: '.implode(', ', $this->send_successes);
					//call user defined function if at least 1 mail was sent successfully
					if(is_callable($this->onSuccessfulSend)) {
						call_user_func($this->onSuccessfulSend);
					}
				}
				
				//create fail info
				if(count($this->send_fails) > 0) {
					$notify_message .= 'Email konnte nicht zugestellt werden an: '.implode(', ', $this->send_fails);
					$color = 'red';
					//call user defined function if at least 1 mail failed to send
					if(is_callable($this->onFailSend)) {
						call_user_func($this->onFailSend);
					}
				}
				
				$modal_chain = new \atk4\ui\jQuery('.atk-modal');
                $modal_chain->modal('hide');
				return [$modal_chain, (new \atk4\ui\jsNotify($notify_message))->setColor($color)->setDuration(10000)];
			});
			
			//button properties
			$this->icon = 'mail';
			if($this->mini_button == false) {
				$this->addClass('primary pmg-button-fullwidth');
				$this->content = $this->text_button;
			}
			else {
				$this->addClass('primary pmg-button-mini');
				$this->content = '';
			}

			$this->on('click', new \atk4\ui\jsModal($this->modal_title, $vp->getURL('cut')));
		}
	}
}

#2

If you want to help create add-on, here is the ticket: https://github.com/atk4/ui/issues/309. Added example class based around something I have used in the past.

It’s important to separate 3 things here:

  • transport. (contains “phpmailer blah blah” code)
  • message. (containing info about what should be sent. I think using anything other than email gateway template APIs is “old school”, so we simply need to pass tags)
  • UI. (allow you to enter text or specify tags)

Of course, if you are building “on-line mailer” then the implementation may be different. My ticket deals with transactional mail, such as password reminders, which is quite commonly used in all web apps.


#3

Hi,

thanks for linking #309.
One question:
What do you mean by

I think using anything other than email gateway template APIs is “old school”, so we simply need to pass tags

Google didn’t give me results that cleared my mind :slight_smile:


#4

Well, conventionally you would put together all your email text and design inside your application through some sort of a template, then send it using SMTP protocol.

With appearance of mail gateways, they have introduced alternative - using API for sending email, rather than SMTP. With this they can make changes to how email is composed.

One of the best change is ability to design email through the gateway’s mail designer / branding tool and simply leave out TAGS which can be populated by your application.

So the template stored on the gateway would be:

Dear {name},

Please click here ({link}) to reset your password.


And the API call will specify name of the template “password reminder” parameter for name= and link=.

There are many benefits, you could integrate design/template with other marketing emails, and your application would only need to know the name of the template.


#5

But I hope based on the separation in 3 parts (transport, message, UI) it would still be easy to use SMTP for sending as an alternative.

The outbox concept looks cool


#6

Ok, it seems I am oldschool :slight_smile: Do you have any example of such an email Gateway? Trying to get “newschool” …


#7

https://sendgrid.com/docs/API_Reference/Web_API_v3/Transactional_Templates/smtpapi.html