I have PHP's mail() using ssmtp which doesn't have a queue/spool, and is synchronous with AWS SES.
I heard I could use SwiftMail to provide a spool, but I couldn't work out a simple recipe to use it like I do currently with mail().
I want the least amount of code to provide asynchronous mail. I don't care if the email fails to send, but it would be nice to have a log.
Any simple tips or tricks? Short of running a full blown mail server? I was thinking a sendmail wrapper might be the answer but I couldn't work out nohup.
You have a lot of ways to do this, but handling thread is not necessarily the right choice.
register_shutdown_function: the shutdown function is called after the response is sent. It's not really asynchronous, but at least it won't slow down your request. Regarding the implementation, see the example.
Swift pool: using symfony, you can easily use the spool.
Queue: register the mails to be sent in a queue system (could be done with RabbitMQ, MySQL, redis or anything), then run a cron that consume the queue. Could be done with something as simple as a MySQL table with fields like from, to, message, sent (boolean set to true when you have sent the email).
Example with register_shutdown_function
<?php
class MailSpool
{
public static $mails = [];
public static function addMail($subject, $to, $message)
{
self::$mails[] = [ 'subject' => $subject, 'to' => $to, 'message' => $message ];
}
public static function send()
{
foreach(self::$mails as $mail) {
mail($mail['to'], $mail['subject'], $mail['message']);
}
}
}
//In your script you can call anywhere
MailSpool::addMail('Hello', 'contact#example.com', 'Hello from the spool');
register_shutdown_function('MailSpool::send');
exit(); // You need to call this to send the response immediately
php-fpm
You must run php-fpm for fastcgi_finish_request to be available.
echo "I get output instantly";
fastcgi_finish_request(); // Close and flush the connection.
sleep(10); // For illustrative purposes. Delete me.
mail("test#example.org", "lol", "Hi");
It's pretty easy queuing up any arbitrary code to processed after finishing the request to the user:
$post_processing = [];
/* your code */
$email = "test#example.org";
$subject = "lol";
$message = "Hi";
$post_processing[] = function() use ($email, $subject, $message) {
mail($email, $subject, $message);
};
echo "Stuff is going to happen.";
/* end */
fastcgi_finish_request();
foreach($post_processing as $function) {
$function();
}
Hipster background worker
Instantly time-out a curl and let the new request deal with it. I was doing this on shared hosts before it was cool. (it's never cool)
if(!empty($_POST)) {
sleep(10);
mail($_POST['email'], $_POST['subject'], $_POST['message']);
exit(); // Stop so we don't self DDOS.
}
$ch = curl_init("http://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
curl_setopt($ch, CURLOPT_TIMEOUT, 1);
curl_setopt($ch, CURLOPT_NOSIGNAL, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, [
'email' => 'noreply#example.org',
'subject' => 'foo',
'message' => 'bar'
]);
curl_exec($ch);
curl_close($ch);
echo "Expect an email in 10 seconds.";
Use AWS SES with PHPMailer.
This way is very fast (hundreds of messages per second), and there isn't much code required.
$mail = new PHPMailer;
$mail->isSMTP(); // Set mailer to use SMTP
$mail->Host = 'ssl://email-smtp.us-west-2.amazonaws.com'; // Specify main and backup SMTP servers
$mail->SMTPAuth = true; // Enable SMTP authentication
$mail->Username = 'blah'; // SMTP username
$mail->Password = 'blahblah'; // SMTP password
$mail->SMTPSecure = 'tls'; // Enable TLS encryption, `ssl` also accepted
$mail->Port = 443;
Not sure if i interpreted your question correctly but i hope this helps.
Pthreads is your friend :)
This is a sample of how i made in my production application
class AsynchMail extends Thread{
private $_mail_from;
private $_mail_to;
private $_subject;
public function __construct($subject, $mail_to, ...) {
$this->_subject = $subject;
$this->_mail_to = $mail_to;
// ...
}
// ...
// you must redefine run() method, and to execute it we must call start() method
public function run() {
// here put your mail() function
mail($this->_mail_to, ...);
}
}
TEST SCRIPT EXAMPLE
$mail_to_list = array('Shigeru.Miyamoto#nintendo.com', 'Eikichi.Kawasaki#neogeo.com',...);
foreach($mail_to_list as $mail_to) {
$asynchMail = new AsynchMail($mail_to);
$asynchMail->start();
}
Let me know if you need further help for installing and using thread in PHP
For logging system, i strongly advice you to use Log4PHP : powerful and easy to use and to configure
For sending mails, i also strongly advice you to use PHPMailer
I'm using asynchronous php execution by using beanstalkd.
It is a simple message queue, really lightweight and easy to integrate.
Using the following php wrapper for php https://github.com/pda/pheanstalk
you can do something as follows to implement a email worker:
use Beanstalk\Client;
$msg="dest_email##email_subject##from_email##email_body";
$beanstalk = new Client();
$beanstalk->connect();
$beanstalk->useTube('flux'); // Begin to use tube `'flux'`.
$beanstalk->put(
23, // Give the job a priority of 23.
0, // Do not wait to put job into the ready queue.
60, // Give the job 1 minute to run.
$msg // job body
);
$beanstalk->disconnect();
Then the job would be done in a code placed into a separate php file.
Something like:
use Beanstalk\Client;
$do=true;
try {
$beanstalk = new Client();
$beanstalk->connect();
$beanstalk->watch('flux');
} catch (Exception $e ) {
echo $e->getMessage();
echo $e->getTraceAsString();
$do = false;
}
while ($do) {
$job = $beanstalk->reserve(); // Block until job is available.
$emailParts = explode("##", $job['body'] );
// Use your SendMail function here
if ($i_am_ok) {
$beanstalk->delete($job['id']);
} else {
$beanstalk->bury($job['id'], 20);
}
}
$beanstalk->disconnect();
You can run separately this php file, as an independent php process. Let's say you save it as sender.php, it would be run in Unix as:
php /path/to/sender/sender.php & && disown
This command would run the file and alsow allow you to close the console or logout current user without stopping the process.
Make sure also that your web server uses the same php.ini file as your php command line interpreter. (Might be solved using a link to you favorite php.ini)
I hope it helps.
Your best bet is with a stacking or spooling pattern. It's fairly simple and can be described in 2 steps.
Store your emails in a table with a sent flag on your current thread.
Use cron or ajax to repeatedly call a mail processing php file that will get the top 10 or 20 unsent emails from your database, flag them as sent and actually send them via your favourite mailing method.
An easy way to do it is to call the code which handles your mails asynchronously.
For example if you have a file called email.php with the following code:
// Example array with e-mailaddresses
$emailaddresses = ['example1#test.com', 'example2#example.com', 'example1#example.com'];
// Call your mail function
mailer::sendMail($emailaddresses);
You can then call this asynchronously in a normal request like
exec('nice -n 20 php email.php > /dev/null & echo $!');
And the request will finish without waiting for email.php to finish sending the e-mails. Logging could be added as well in the file that does the e-mails.
Variables can be passed into the exec between the called filename and > /dev/null like
exec('nice -n 20 php email.php '.$var1.' '.$var2.' > /dev/null & echo $!');
Make sure these variables are safe with escapeshellarg(). In the called file these variables can be used with $argv
Welcome to async PHP
https://github.com/shuchkin/react-smtp-client
$loop = \React\EventLoop\Factory::create();
$smtp = new \Shuchkin\ReactSMTP\Client( $loop, 'tls://smtp.google.com:465', 'username#gmail.com','password' );
$smtp->send('username#gmail.com', 'sergey.shuchkin#gmail.com', 'Test ReactPHP mailer', 'Hello, Sergey!')->then(
function() {
echo 'Message sent via Google SMTP'.PHP_EOL;
},
function ( \Exception $ex ) {
echo 'SMTP error '.$ex->getCode().' '.$ex->getMessage().PHP_EOL;
}
);
$loop->run();
Related
When I'm building and testing my website on local server, I would like to emulate successful sending via phpmailer, so I avoid actually sending emails.
I normally use if ($mail->Send()) { the mail was sent, now do this }.
For local testing I think the best would be to skip the whole phpmailer inclusion, instead of adding a lot of if statements etc.
But skipping phpmailer would then cause php to complain about $mail->Send(), $mail->addAddress('emailaddress') etc.
How could I fake the function (or object/class) so that calls to $mail->Send() are always true, and the rest $mail->something() etc. are just ignored/true, so that no email is sent?
Extend the PHPMailer class and override the public function send().
class UnitTestMailer extends PHPMailer {
public function send() {
return $this;
}
}
class User {
public function __construct(PHPMailer $mailer) {
$this->mailer = $mailer;
}
public function sendActiviation() {
return $this->mailer->send();
}
}
// ... somewhere in your test
public function test_if_from_is_properly_set() {
// ...
$user = new User(new UnitTestMailer);
// ...
$mailer = $user->sendActivation();
$this->assertEquals($expectedFrom, $mailer->From);
}
Why emulate?
I use INI files to provide configuration variables for PHPMailer depending on the environment. Live obviously has the server's mail settings. Local uses my Gmail account credentials to send mail.
You can have the best of both worlds :)
Keep a config.ini file for example somewhere in your working directory (I tend to use root but that's preference) and make sure it's in your .gitignore or similar. Your local version would look something like:
[PHPMailer Settings]
EMAIL_HOST = "smtp.gmail.com"
EMAIL_PORT = 587
EMAIL_SMTPSECURE = "tls"
EMAIL_SMTPAUTH = "true"
EMAIL_USERNAME = "my_email#gmail.com"
EMAIL_PASSWORD = "my_password"
then in your PHP:
$ini = parse_ini_file($_SERVER['DOCUMENT_ROOT'] . "/config.ini", true, INI_SCANNER_TYPED);
// use settings from INI file:
$foo = $ini['PHPMailer Settings']['EMAIL_HOST'];
$bar = $ini['PHPMailer Settings']['EMAIL_PORT'];
Security Bonus
Change the syntax of your INI file to look like the below and rename it to config.ini.php:
; <?php
; die();
; /*
[PHPMailer Settings]
EMAIL_HOST = "smtp.gmail.com"
EMAIL_PORT = 587
EMAIL_SMTPSECURE = "tls"
EMAIL_SMTPAUTH = "true"
EMAIL_USERNAME = "my_email#gmail.com"
EMAIL_PASSWORD = "my_password"
; */ ?>
(remember to use the new filename in your PHP code)
PHP can still parse the settings, but if anyone tried to access the INI file it would be parsed as PHP comments and just show ";"
I have been following along with the tutorials here and got the ratchet server working.
My chat class is the same as the tutorial more or less at the moment, so no point in showing that here yet since my question is more about implementation strategy.
In the question I attached the user was looking how to get the connection object of a specific user. In the top answer solution keeping track of the resource IDs seems to be the way to do this.
For example when the connection is created there is this code.
public function onOpen(ConnectionInterface $conn) {
// Store the new connection to send messages to later
$this->clients[$conn->resourceId] = $conn;
echo "New connection! ({$conn->resourceId})\n";
}
This creates a member variable clients to store all the connections and you simply reference it now by ID to send a message. This clients however is an instance of ConnectionInterface $conn
Then to send a message you simply use the code below entering as the array key the id of the client. Very simple.
$client = $this->clients[{{insert client id here}}];
$client->send("Message successfully sent to user.");
As we know ratchet runs as a script on the server in an event loop never ending.
I'm running a Symfony project in which outside of the server instance running the ratchet code when a user does a certain action in the system I need it to send a message to a particular client connected to the server.
I'm not sure how to do this since the clients are instances of ConnectionInterface and are created when the users first connect via WebSockets. How do I send a message to a particular client in this way?
Here is a visual of what I'm trying to achieve.
References:
how to get the connection object of a specific user?
The solution I am about to post covers the entire process of communicating from server to the client on the web browser including a way to make the Websocket server run in the background (with and without docker).
Step 1:
Assuming you have ratchet installed via composer, create a folder in your project called bin and name the file "startwebsocketserver.php" (or whatever you want)
Step 2:
Copy the following code into it.
<?php
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use React\Socket\Server;
use React\EventLoop\Factory;
use WebSocketApp\Websocketserver;
use WebSocketApp\Htmlserver;
use WebSocketApp\Clientevent;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Ratchet\App;
require dirname(__DIR__) . '/vendor/autoload.php';
require_once dirname(__DIR__) . '/bootstrap/bootstrap.php';
$websocketserver = new Websocketserver();
$dispatcher = new EventDispatcher(); //#JA - This is used to maintain communication between the websocket and HTTP Rest API Server
$dispatcher->addListener('websocketserver.updateclient', array($websocketserver, 'updateClient'));
//// 1. Create the event loop
$loop = Factory::create();
//// 2. Create websocket servers
$webSock = new Server($loop);
new IoServer(
new HttpServer(
new WsServer( $websocketserver )
),
$webSock
);
$webSock->listen('8080', '0.0.0.0');
$app = new App( 'localhost', 6677, '0.0.0.0',$loop );
$app->route( '/', new Htmlserver(), [ '*' ] );//#JA - Allow any origins for last parameter
$app->run();
Note that in my example I am using a bootstrap file to load the database. If you are not using a database or some other method than ignore that. For the purposes of this answer I will be assuming Doctrine 2 as the database.
What this code does is creates an HTTP server & a WebSocket server within the same code base and at the same time. I'm using the $app->route approach since you can add further routing for the HTTP server to organize API Calls to talk to the WebSocket Server from your PHP Web Server.
The $loop variable includes the Websocket server in the application loop along with the HTTPServer.
Step 3:
In your project directory create a folder called websockets. Inside that create another folder called WebSocketApp. Inside that create 3 empty files for now.
Clientevent.php
Htmlserver.php
Websocketserver.php
We will go into each of these files 1 by 1 next. Failure to create these directories in this order will cause composer Autoload PSR-0 to fail to find them.
You can change the names but make sure you edit your composer file accordingly.
Step 4:
In your composer.json file make sure it looks something like this.
{
"require": {
"doctrine/orm": "^2.5",
"slim/slim": "^3.0",
"slim/twig-view": "^2.1",
"components/jquery": "*",
"components/normalize.css": "*",
"robloach/component-installer": "*",
"paragonie/random_compat": "^2.0",
"twilio/sdk": "^5.5",
"aws/aws-sdk-php": "^3.22",
"mailgun/mailgun-php": "^2.1",
"php-http/curl-client": "^1.7",
"guzzlehttp/psr7": "^1.3",
"cboden/ratchet": "^0.3.6"
},
"autoload": {
"psr-4": {
"app\\":"app",
"Entity\\":"entities"
},
"psr-0": {
"WebSocketApp":"websockets"
},
"files": ["lib/utilities.php","lib/security.php"]
}
}
In my case I'm using doctrine & slim, the important part is the "autoload" section. This section in particular is important.
"psr-0": {
"WebSocketApp":"websockets"
},
This will autoload anything in the folder websockets in the namespace of WebSocketApp. psr-0 assumed that code would be organized by folders for namespaces which is why we had to add another folder called WebSocketApp inside of websockets.
Step 5:
In the htmlserver.php file put this...
<?php
namespace WebSocketApp;
use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\Message\Response;
use Guzzle\Http\Message\Request;
use Ratchet\ConnectionInterface;
use Ratchet\Http\HttpServerInterface;
class Htmlserver implements HttpServerInterface {
protected $response;
public function onOpen( ConnectionInterface $conn, RequestInterface $request = null ) {
global $dispatcher;
$this->response = new Response( 200, [
'Content-Type' => 'text/html; charset=utf-8',
] );
$query = $request->getQuery();
parse_str($query, $get_array);//#JA - Convert query to variables in an array
$json = json_encode($get_array);//#JA - Encode to JSON
//#JA - Send JSON for what you want to do and the token representing the user & therefore connected user as well.
$event = new ClientEvent($json);
$dispatcher->dispatch("websocketserver.updateclient",$event);
$this->response->setBody('{"message":"Successfully sent message to websocket server")');
echo "HTTP Connection Triggered\n";
$this->close( $conn );
}
public function onClose( ConnectionInterface $conn ) {
echo "HTTP Connection Ended\n";
}
public function onError( ConnectionInterface $conn, \Exception $e ) {
echo "HTTP Connection Error\n";
}
public function onMessage( ConnectionInterface $from, $msg ) {
echo "HTTP Connection Message\n";
}
protected function close( ConnectionInterface $conn ) {
$conn->send( $this->response );
$conn->close();
}
}
The purpose of this file is to make communication to the WebSocket server simple through basic HTTP which I will show a demo of later using cURL from the PHP Web Server. I designed this to propagate messages to the WebSocket server using Symfony's Event system and by looking at the Query String and converting it to a JSON string. It could have also been kept as an array if you wish, but in my case I needed the JSON string.
Step 6:
Next in the clientevent.php put this code...
<?php
namespace WebSocketApp;
use Symfony\Component\EventDispatcher\Event;
use Entity\User;
use Entity\Socket;
class Clientevent extends Event
{
const NAME = 'clientevent';
protected $user; //#JA - This returns type Entity\User
public function __construct($json)
{
global $entityManager;
$decoded = json_decode($json,true);
switch($decoded["command"]){
case "updatestatus":
//Find out what the current 'active' & 'busy' states are for the userid given (assuming user id exists?)
if(isset($decoded["userid"])){
$results = $entityManager->getRepository('Entity\User')->findBy(array('id' => $decoded["userid"]));
if(count($results)>0){
unset($this->user);//#JA - Clear the old reference
$this->user = $results[0]; //#JA - Store refernece to the user object
$entityManager->refresh($this->user); //#JA - Because result cache is used by default, this will make sure the data is new and therefore the socket objects with it
}
}
break;
}
}
public function getUser()
{
return $this->user;
}
}
Note that the User and Socket entities are entities I created from Doctrine 2. You can use whatever database you prefer. In my case I am needing to send messages to particular users from the PHP Web Server based on their login tokens from the database.
Clientevent assumes JSON string of '{"command":"updatestatus","userid":"2"}'
You can set it up however you like though.
Step 7:
In the Websocketserver.php file put this...
<?php
namespace WebSocketApp;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Symfony\Component\EventDispatcher\Event;
use Entity\User;
use Entity\Authtoken;
use Entity\Socket;
class Websocketserver implements MessageComponentInterface {
protected $clients;
public function updateClient(Event $event)
{
$user = $event->getUser();//#JA - Get reference to the user the event is for.
echo "userid=".$user->getId()."\n";
echo "busy=".($user->getBusy()==false ? "0" : "1")."\n";
echo "active=".($user->getActive()==false ? "0" : "1")."\n";
$json["busy"] = ($user->getBusy()==false ? "0" : "1");
$json["active"] = ($user->getActive()==false ? "0" : "1");
$msg = json_encode($json);
foreach($user->getSockets() as $socket){
$connectionid = $socket->getConnectionid();
echo "Sending For ConnectionID:".$connectionid."\n";
if(isset($this->clients[$connectionid])){
$client = $this->clients[$connectionid];
$client->send($msg);
}else{
echo "Client is no longer connected for this Connection ID:".$connectionid."\n";
}
}
}
public function __construct() {
$this->clients = array();
}
public function onOpen(ConnectionInterface $conn) {
// Store the new connection to send messages to later
$this->clients[$conn->resourceId] = $conn;
echo "New connection! ({$conn->resourceId})\n";
}
public function onMessage(ConnectionInterface $from, $msg) {
global $entityManager;
echo sprintf('Connection %d sending message "%s"' . "\n", $from->resourceId, $msg);
//#JA - First step is to decode the message coming from the client. Use token to identify the user (from cookie or local storage)
//#JA - Format is JSON {token:58d8beeb0ada3:4ffbd272a1703a59ad82cddc2f592685135b09f2,message:register}
$json = json_decode($msg,true);
//echo 'json='.print_r($json,true)."\n";
if($json["message"] == "register"){
echo "Registering with server...\n";
$parts = explode(":",$json["token"]);
$selector = $parts[0];
$validator = $parts[1];
//#JA - Look up records in the database by selector.
$tokens = $entityManager->getRepository('Entity\Authtoken')->findBy(array('selector' => $selector, 'token' => hash('sha256',$validator)));
if(count($tokens)>0){
$user = $tokens[0]->getUser();
echo "User ID:".$user->getId()." Registered from given token\n";
$socket = new Socket();
$socket->setUser($user);
$socket->setConnectionid($from->resourceId);
$socket->setDatecreated(new \Datetime());
$entityManager->persist($socket);
$entityManager->flush();
}else{
echo "No user found from the given cookie token\n";
}
}else{
echo "Unknown Message...\n";
}
}
public function onClose(ConnectionInterface $conn) {
global $entityManager;
// The connection is closed, remove it, as we can no longer send it messages
unset($this->clients[$conn->resourceId]);
//#JA - We need to clean up the database of any loose ends as well so it doesn't get full with loose data
$socketResults = $entityManager->getRepository('Entity\Socket')->findBy(array('connectionid' => $conn->resourceId));
if(count($socketResults)>0){
$socket = $socketResults[0];
$entityManager->remove($socket);
$entityManager->flush();
echo "Socket Entity For Connection ID:".$conn->resourceId." Removed\n";
}else{
echo "Was no socket info to remove from database??\n";
}
echo "Connection {$conn->resourceId} has disconnected\n";
}
public function onError(ConnectionInterface $conn, \Exception $e) {
echo "An error has occurred: {$e->getMessage()}\n";
$conn->close();
}
}
This is the most complicated file to explain. To start there is a protected variable clients that stores every connection made to this ratchet websocket server. Its created in the onOpen event.
Next the onMessage event is where the web browser clients will register themselves for receiving messages. I did this using a JSON protocol. An example is in the code of the format I used in particular in which I used the token from their cookie to identify what user it was in my system along with a simple register message.
I simple look in the database in this function to see if there is an authToken to go along with the cookie.
If there is write to the Socket table in your database the $from->resourceId
This is the number that ratchet uses to keep track of that particular connection number.
Next in the onClose method note that we have to make sure to remove the entries we created when the connection closes so the database doesn't get filled with unnecessary and extra data.
Finally note that the updateClient function is a symfony Event that is triggered from the HtmlServer we did earlier.
This is what actually sends the message to the client web browser. First in case that user has many web browsers open creating different connections we loop through all known sockets related to that user. Doctrine makes this easy with $user->getSockets(), you will have to decide best way to do this.
Then you simply say $client->send($msg) to send the message to the web browser.
Step 8:
Finally in your javascript for your webbrowser put something like this.
var hostname = window.location.hostname; //#JA - Doing it this way will make this work on DEV and LIVE Enviroments
var conn = new WebSocket('ws://'+hostname+':8080');
conn.onopen = function(e) {
console.log("Connection established!");
//#JA - Register with the server so it associates the connection ID to the supplied token
conn.send('{"token":"'+$.cookie("ccdraftandpermit")+'","message":"register"}');
};
conn.onmessage = function(e) {
//#JA - Update in realtime the busy and active status
console.log(e.data)
var obj = jQuery.parseJSON(e.data);
if(obj.busy == "0"){
$('.status').attr("status","free");
$('.status').html("Free");
$(".unbusy").css("display","none");
}else{
$('.status').attr("status","busy");
$('.status').html("Busy");
$(".unbusy").css("display","inline");
}
if(obj.active == "0"){
$('.startbtn').attr("status","off");
$('.startbtn').html("Start Taking Calls");
}else{
$('.startbtn').attr("status","on");
$('.startbtn').html("Stop Taking Calls");
}
};
My demo here shows simple way to pass information back and forth with JSON.
Step 9:
To send messages from the PHP Web server I did something like this in a helper function.
function h_sendWebsocketNotificationToUser($userid){
//Send notification out to Websocket Server
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://localhost/?command=updatestatus&userid=".$userid);
curl_setopt($ch, CURLOPT_PORT, 6677);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
curl_close($ch);
}
This would attempt to send the updateStatus message for a particular user at anytime.
Step 10:
There is no step 10 you are done! Well okay not quite... To run the webserver in the background I use Docker which makes it easy. Simply execute the webserver with the following command.
docker exec -itd draftandpermit_web_1 bash -c "cd /var/www/callcenter/livesite; php bin/startwebsocketserver.php"
or something of this equivlent for your situation. Key here is the -d option I'm using which runs it in the background. Even if you run the command again it will NOT spawn two instances which is nifty. Shutting down the server is outside the scope of this but if you find a nice way to do this please amend or comment on this answer.
Also don't forget to open the ports correctly on your docker-compose file. I did something like this for my project.
ports:
- "80:80"
- "8080:8080"
- "6060:80"
- "443:443"
- "6677:6677"
#This is used below to test on local machines, just portforward this on your router.
- "8082:80"
Just remember 8080 is used by the WebSockets so it has to pass through completely.
In case you are curious about entity and database structure and what I used here is an attached image.
I was following PHPMailer tutorial and some tutorials in Internet but I still can't make execution less than 2 second. On many website it says it shouldn't take more than 0.4s. I tried it from my local machine and from AWS machine. Execution time same.
class BatchMailer {
private static $mail;
private static $initialized = false;
private static function initialize() {
if (self::$initialized)
return;
self::$mail = new PHPMailer;
self::$mail->SMTPDebug = 2;
self::$mail->isSMTP();
self::$mail->Host = 'smtp.gmail.com';
self::$mail->Port = 587;
self::$mail->SMTPSecure = 'tls';
self::$mail->SMTPAuth = true;
self::$mail->Username = '***';
self::$mail->Password = '***';
self::$mail->SMTPKeepAlive = true;
self::$mail->setFrom('***#gmail.com', 'Title');
self::$mail->isHTML(true);
self::$mail->AltBody = 'Please use an HTML-enabled email client to view this message.';
self::$initialized = true;
}
public static function setSubject($subject) {
self::initialize();
self::$mail->Subject = $subject;
}
public static function setBody($body) {
self::initialize();
self::$mail->Body = stripslashes($body);
}
public static function sendTo() {
self::initialize();
self::$mail->clearAddresses();
$recipients = array(
'***#gmail.com' => 'Person One'
);
foreach($recipients as $email => $name) {
self::$mail->AddCC($email, $name);
}
self::$mail->send();
return;
}
static function test() {
self::setSubject('subject');
self::setBody('body');
self::sendTo();
}
}
SMTP is often slow, especially when things like greetdelay/tarpitting are used as anti-spam measures. 2 seconds is not that slow - the SMTP spec allows for timeouts of 10-20 minutes! It's really unsuited to real-time use, i.e. during a web page submission, but that doesn't seem to stop many trying to use it that way. To maximise performance you can install a local mail server to use as a relay, or hand off your message send to a separate process that doesn't mind waiting for a while, for example by submitting using an async ajax request from your page so the user is not blocked from doing other things.
If you're sending larger volumes of email it's important to use a relay and SMTP keepalive while submitting it. I have no trouble sustaining over 200 messages/second with PHPMailer.
Nice class BTW - tidier than most of the things I see on SO! $initialized is not needed - just check whether self::$mail is set instead.
New to PHP and Swiftmailer and have yet to get it working. I've uploaded the /lib/ directory to a directory in the root of my shared webserver from Hostgator. I've uploaded the following inside in the directory above /lib/:
<?php
require_once 'lib/swift_required.php';
$transport = Swift_SmtpTransport::newInstance('mail.****.com', 25)
->setUsername('****#****.com')
->setPassword('****');
$mailer = Swift_Mailer::newInstance($transport);
$message = Swift_Message::newInstance('Subject Here')
->setFrom(array('****#****.com' => '****'))
->setTo(array('****#****.com' => '****'));
$message->setBody('This is the message');
if (!$mailer->send($message, $errors))
{
echo "Error:";
print_r($errors);
}
?>
It does not send a message, but I am also unable to view any error logs. I have error logging enabled in all of sections in my php.ini - but when I try to go to where I uploaded the .php file in a browser I get a 404 error. When I connect through ssh I have jailshell access. When I tried to go to /var/log/php-scripts.log I did not have permission. Wondering where else I could find errors for this in order to fix it?
You should try to use swiftmailer logger plugin.
The Logger plugins helps with debugging during the process of sending. It can help to identify why an SMTP server is rejecting addresses, or any other hard-to-find problems that may arise.
You only need to create new logger instance and add it to mailer object with registerPlugin() method.
<?php
require_once 'lib/swift_required.php';
$transport = Swift_SmtpTransport::newInstance('mail.****.com', 25)
->setUsername('****#****.com')
->setPassword('****');
$mailer = Swift_Mailer::newInstance($transport);
$logger = new \Swift_Plugins_Loggers_ArrayLogger();
$mailer->registerPlugin(new \Swift_Plugins_LoggerPlugin($logger));
$message = Swift_Message::newInstance('Subject Here')
->setFrom(array('****#****.com' => '****'))
->setTo(array('****#****.com' => '****'));
$message->setBody('This is the message');
if (!$mailer->send($message, $errors)) {
// Dump the log contents
// NOTE: The EchoLogger dumps in realtime so dump() does nothing for it. We use ArrayLogger instead.
echo "Error:" . $logger->dump();
}else{
echo "Successfull.";
}
?>
Edit :
Swift_Plugins_Loggers_ArrayLogger: Keeps a collection of log messages inside an array. The array content can be cleared or dumped out to the screen.
Swift_Plugins_Loggers_EchoLogger: Prints output to the screen in realtime. Handy for very rudimentary debug output.
Use transport exception handling like so:
try {
$mailer->send($message);
}
catch (\Swift_TransportException $e) {
echo $e->getMessage();
}
Under your swift mailer extension there is an exception handler.
root/lib/classes/Swift/SwiftException.php
By default it does some convoluted things with errors to make it really hard to find them. Most of the pages out there also recommend equally convoluted ways of logging the errors.
If you just want to see the error, add an echo, like below (or whatever else you might want to do with it).
class Swift_SwiftException extends Exception
{
/**
* Create a new SwiftException with $message.
*
* #param string $message
*/
public function __construct($message)
{
echo $message;
parent::__construct($message);
}
}
I wanna try to send mail using cake php. I have no experience of sending mail. So, I don't know where to start. Is it need to make mail server? If need, how to make mail server and how to send mail? Please explain step by step. I really don't know where to start.
I'm using xampp and now I test my site at localhost.
I tested following link:
http://book.cakephp.org/view/1286/Sending-a-basic-message
but error occurred cannot be accessed directly.
and then I add code from the following link:
http://book.cakephp.org/view/1290/Sending-A-Message-Using-SMTP
So, my code is following:
function _sendMail(){
$this->Email->to = 'user#gmail.com';
$this->Email->bcc = array('secret#example.coom');
$this->Email->subject = 'Welcome to our really cool things';
$this->Email->replyTo = 'support#example.com';
$this->Email->from = 'Online Application <app#example.coom>';
$this->Email->template = 'simple_message';
$this->Email->sendAs = 'both';
$this->Email->smtpOptions = array(
'port' =>'25',
'timeout' => '30',
'host' => 'ssl://smtp.gmail.com',
'username' => 'my_mail#gmail.com',
'password' =>'aaa',
);
$this->Email->delivery = 'smtp';
$this->Email->send();
}
but error still occurred. But, I didn't make any mail server.Is that OK?
I have a feeling this is to do with your XAMPP configuration:
Try opening "php.ini", it should be somewhere in your server files.
Search for the attribute called “SMTP” in the php.ini file.Generally you can find the line “SMTP=localhost“. change the localhost to the smtp server name of your ISP. And, there is another attribute called “smtp_port” which should be set to 25.I’ve set the following values in my php.ini file.
SMTP = smtp.wlink.com.np
smtp_port = 25
Restart the apache server so that PHP modules and attributes will be reloaded.
ow try to send the mail using the mail() function:
mail(“you#yourdomain.com”,”test subject”,”test body”);
If you get the following warning:
Warning: mail() [function.mail]: “sendmail_from” not set in php.ini or custom “From:” header missing in C:\Program Files\xampp\htdocs\testmail.php on line 1
Specify the following headers and try to send the mail again:
$headers = ‘MIME-Version: 1.0′ . “\r\n”;
$headers .= ‘Content-type: text/html; charset=iso-8859-1′ . “\r\n”;
$headers .= ‘From: sender#sender.com’ . “\r\n”;
mail(“you#yourdomain.com”,”test subject”,”test body”,$headers);
source: http://roshanbh.com.np/2007/12/sending-e-mail-from-localhost-in-php-in-windows-environment.html
Naming the controller function with a leading underscore is Cake's backwards compatible way of designating that the function should be protected, i.e. that the function should not be accessible as a normal controller action. That means you can't access FooController::_sendMail() using the URL /foo/_sendMail, or any other URL for that matter. You should be seeing this, which IMO is a pretty good error message:
Private Method in UsersController
Error: FooController::_sendMail() cannot be accessed directly.
Remove the leading underscore, that's all. This problem has nothing to do with sending email.
Try:
//load mail component in controller
var $components = array('Mail');
//then do
$this->Email->sendAs="html";
$this->Email->from="some#domain.com";
$this->Email->to="someone#domain.com";
$this->Email->subject="Your subject;
$this->Email->send("Your message);
//Check cakephp manual for more reference: http://book.cakephp.org/
Here is an example code for sending an HTML email with CakePHP's Email component
Ex controller : EmailController.php
<?php
class EmailController extends AppController{
public $components=array('Email');
function send(){
//create an array of values to be replaced in email html template//
$emailValues=array('name'=>'MyName','phone'=>'MyPhone');
$this->set('emailValues',$emailValues);//pass to template //
$this->Email->to = 'to#address.com'; //receiver email id
$this->Email->subject = 'Subject Line';
$this->Email->replyTo = 'reply#address.com'; //reply to email//
$this->Email->from = 'SenderName<sender#address.com>'; //sender
$this->Email->template = 'sample';//email template //
$this->Email->sendAs = 'html';
if($this->Email->send()){
//mail send //
}
}
?>
Now create the email template in folder /Views/Email/html/
ie the template path should be
/Views/Email/html/sample.ctp
sample.ctp
<?php
Hi <?php echo $emailValues['name'];?> <br>
Thanks for sharing your phone number '<?php echo $emailValues['phone'];?>' .
<br>
?>