Sending email using MVC

I am not getting any errors currently and am even getting the success message from php mailer but no email is actually being sent.

Here is my email class:

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;

require '../vendor/autoload.php';

class Email {
	
	public function sendMail($email)
	{
	
		$mail = new PHPMailer(true);                              // Passing `true` enables exceptions
		try {
			//Server settings
//			$mail->SMTPDebug = 2;                                 // Enable verbose debug output
			$mail->isSMTP();                                      // Set mailer to use SMTP
			$mail->Host = 'mail.example.com';  // Specify main and backup SMTP servers
			$mail->SMTPAuth = true;                               // Enable SMTP authentication
			$mail->Username = mail@example.com';                 // SMTP username
			$mail->Password = 'secret';                           // SMTP password
			$mail->SMTPSecure = 'tls';                            // Enable TLS encryption, `ssl` also accepted
			$mail->Port = 587;                                    // TCP port to connect to

			//Recipients
			$mail->setFrom('mail@example.com');
			$mail->addAddress($email);     // Add a recipient              // Name is optional
			$mail->addReplyTo('mail@example.com');


			//Content
			$mail->isHTML(true);                                  // Set email format to HTML
			$mail->Subject = 'Forgotten password';
			$mail->Body    = 'This is the HTML message body <b>in bold!</b>';
			$mail->AltBody = 'This is the body in plain text for non-HTML mail clients';

			$mail->send();
		} catch (Exception $e) {
			echo 'Message could not be sent.';
			echo 'Mailer Error: ' . $mail->ErrorInfo;
		}	
	}
}

Open in new window


In my controller I have:

      
public function forgot_password()
	{
		if($_SERVER['REQUEST_METHOD'] == 'POST') {
			
			$email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);
			
			$data = [
				
				'email' => $email,
				'message' => ''
			];
			
			if(!filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) {
				
				$data['message'] .= 'Invalid email address';
			}
			
			if(!$this->userModel->findUserByEmail($email) && filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) {
				
				$data['message'] .= 'Email address does not exist';
			}
			
			if(!empty($data['message'])) {
				
				$this->view('users/forgot-password', $data);
				
			} else {
				
				$send = new Email();
				$send->sendMail($data['email']);
				$this->view('users/email-sent');
			
			}
			
		} else {
			
			$data = [
				
				'email' => '',
				'message' => ''
			];
			
			$this->view('users/forgot-password', $data);
				
		}	
	}

Open in new window


The email never sends but I can't figure out why.
LVL 1
Black SulfurAsked:
Who is Participating?

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

x
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

gr8gonzoConsultantCommented:
So first thing I would do is change the try/catch so that it passes the exception up to the parent, and also catch global exceptions:

OLD:
} catch (Exception $e) {
    echo 'Message could not be sent.';
    echo 'Mailer Error: ' . $mail->ErrorInfo;
}

Open in new window


NEW:
} catch (Exception $e) {
    throw new \Exception($e->errorMessage()); // Wrap it into a global exception
}
} catch (\Exception $e) { // Global exception - see the leading slash
    throw $e;
}

Open in new window


Then in your controller, use a try/catch to determine which view to show.
try
{
    $send = new Email();
    $send->sendMail($data['email']);
    $this->view('users/email-sent');
}
catch(\Exception $ex) // Global exception again here
{
    echo $ex->getMessage();
    die(); 

    // Later on, maybe pass the message into a "email not sent" view:
    $this->view('users/email-not-sent');   // Or whatever your view name is
}

Open in new window

0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
Black SulfurAuthor Commented:
Thanks gr8gonzo.

I am getting an error from your code labelled NEW:

} catch (Exception $e) {
    throw new \Exception($e->errorMessage()); // Wrap it into a global exception
}
} catch (\Exception $e) { // Global exception - see the leading slash
    throw $e;
}

Open in new window


Parse error: syntax error, unexpected 'catch' (T_CATCH), expecting function (T_FUNCTION)
0
gr8gonzoConsultantCommented:
Sorry - I had a typo. I have too many }. Just remove line 3 so it looks like this:

} catch (Exception $e) {
    throw new \Exception($e->errorMessage()); // Wrap it into a global exception
} catch (\Exception $e) { // Global exception - see the leading slash
    throw $e;
}

Open in new window

0
OWASP: Threats Fundamentals

Learn the top ten threats that are present in modern web-application development and how to protect your business from them.

Black SulfurAuthor Commented:
Great, thanks. What does the Exception with the \ do?
0
Black SulfurAuthor Commented:
So, this is weird. If I try send the email to an email address at a different domain I have, I get the success message but never receive any emails. If I change it to my gmail account I get the emails. Any ideas?
0
gr8gonzoConsultantCommented:
It's about namespaces. When you start using the object-oriented programming features of PHP, like Exception objects, you have the ability to organize code using namespaces. It seems PHPMailer is setting up its own Exception class inside of its own namespace, so you have two classes that are both called "Exception" - there's the PHPMailer one which is defined in the PHPMailer namespace, and then there's the default global one, which doesn't have any specific namespace.

When you add a slash at the beginning of a class name, you're telling PHP to look at the Exception class that doesn't belong to any specific namespace (the default PHP exception).

When you're already working inside of a namespace in your code and you DON'T have a slash, then PHP will look for that class inside the current namespace. So when you're working in the PHPMailer namespace, then doing something like "new Foo()" would look for a class named "Foo" inside the current PHPMailer namespace.

If you wanted to avoid assumptions, you could provide paths every time, like:

} catch(\PHPMailer\Exception $e) {
    ...
} catch(\Exception) {
   ...
}

It works similar to folder structures / paths in most operating systems, if that helps any.
1
gr8gonzoConsultantCommented:
Well, you're getting the success message because your code doesn't have any logic to NOT show a success message. You're showing the success view every time, even if the code fails. That's why I was suggesting my change - so your controller can know when something fails and show a different screen.

As far as the different email goes, yeah, the problem is almost guaranteed to be a spam filter issue. There's a variety of things that go into proper email delivery. As the internet continues to fill up with spammers, there's more and more hoops to jump through to prevent people from casually being able to send out emails to (and from) whomever they want.

If I had to guess, you're probably sending email from a machine that doesn't have a PTR / reverse DNS record set up, and probably doesn't use any sort of SPF or DKIM. And if you're on a non-static IP (e.g. a home internet connection or something similar), that will count against you, too. PHPMailer is designed to go straight to the recipient's mail server and try to deliver mail directly, so it gets analyzed more heavily before a successful delivery is allowed.

There's also a smaller chance of a DNS resolution problem. If you're sending to joe@helloworld.com and your server can't resolve helloworld.com to an IP address or can't get the MX records for the helloworld.com domain, then it can't send mail to it.

Check out my article on this topic:
https://www.experts-exchange.com/articles/10089/Trouble-Sending-Mail-from-Your-Script.html

I also have a more in-depth article on email delivery issues if you're still having problems after the above article's suggestions.
https://www.experts-exchange.com/articles/1222/16-Tips-to-Improve-Email-Delivery.html
0
Black SulfurAuthor Commented:
I have already implemented your new code and still get the success messages about the email being sent (this line gives me all the info)

$mail->SMTPDebug = 3;  

Open in new window


Not sure if it makes a difference, but I am also doing this on localhost/MAMP, not on a live server.

Lastly, would it be better to use something like Mailgun? I looked at it but it seems complicated having to fiddle with the DNS.
0
gr8gonzoConsultantCommented:
On a side note, I should also point out that if you're looking to implement correct MVC structure, just remember that all of your output should be coming from your VIEW and not from libraries or controllers or anything. For example, this is wrong:

} catch (Exception $e) {
    echo 'Message could not be sent.';
    echo 'Mailer Error: ' . $mail->ErrorInfo;
}

...because it's trying to output content directly from the functional code.
1
gr8gonzoConsultantCommented:
Not sure if it makes a difference, but I am also doing this on localhost/MAMP, not on a live server.
Yes, absolutely that makes a difference. You have to look at the world through the eyes of a mail server administrator who is trying to stop spam. Imagine you're getting these messages that:

  1. don't look like NORMAL e-mail messages (they don't have all the headers that are commonly added by email programs),
  2. the messages are not being sent via a trusted mail server,
  3. and the messages are coming from something that is basically anonymous (a dynamic connection without any permanent reverse DNS).

It looks and smells exactly like spam, even if the content itself doesn't really have anything spammy in it. It just has all the characteristics of a message sent by a spammer, so it's going to get blocked.

I don't have any experience with Mailgun, but it sounds like it might work if they have their own mail domain that your emails come from.

Alternatively, if you have a live server that is running SMTP and has a protected relay (which means you can use it to send emails as long as you login first with a username/password), then you can tell PHPMailer to use that server for your SMTP server, and it would also probably resolve the problem.
0
Black SulfurAuthor Commented:
Thank you!
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
PHP

From novice to tech pro — start learning today.