DEV Community

Cover image for Send Gmail emails from a dockerized PHP app' the easy and free way
yactouat
yactouat

Posted on • Updated on

Send Gmail emails from a dockerized PHP app' the easy and free way

While web API's like SendGrid are great tools for emailing at scale from within a web application, they are not free. Also, they often provide overkill features when you just want to send plain confirmation emails, for instance.

What we are going to look at in this post is how to send an email using a Gmail account programmatically with a PHP app' running in Docker. This may pave you a way to build a mailer service that you could later run in Kubernetes or Google Cloud Run or any other platform of your choice.

prerequisites

  • some basic knowledge of Linux
  • some basic knowledge of Docker
  • some basic knowledge of PHP
  • some basic knowledge of nginx

let's start with configuring your Gmail account

If you want like to send emails via a program using your Gmail address, you'll need to configure this in your Google account UI to allow such a program to access your account.

  1. Go to your Google account, for instance by clicking on the top right profile menu in your Google Chrome browser
  2. Go the security menu Image description
  3. In the Signing in to Google menu, enable 2FA Image description
  4. Once it's done, in the same menu, go to App passwords
  5. From there you will be able to generate a password for your application Image description
  6. I usually choose Other in the Select app and Select device dropdowns for my custom apps; also I like to keep track of such passwords by giving them a name that is explicit as to where they are used
  7. Once you have generated your password, make sure you copy it to be persisted somewhere because you wont be able to see it again

Now that the configuring the Google account part is done, let's write the Dockerfile to be able to send emails using your Gmail account from within a container.

the Docker part: configuring msmtp

msmtp is a piece of software that acts as an SMTP client. SMTP stands for Simple Mail Transfer Protocol; this protocol can be used to implement a mail server to send and receive emails or it can be used to relay emails to actual servers. We are going to do the latter by relaying outgoing emails via a dockerized msmtp client to Gmail's SMTP server for final delivery of the email you're sending.

Let's set up our Docker environment with a docker-compose.yml file at the root of a folder that will contain this project. Please take a look at this commented file:

services:

  # name of our service
  mailer_service:
    # giving to docker-compose the path of our custom Dockerfile that will define our service image
    build:
      context: .
      dockerfile: mailer_service.Dockerfile
    # setting the working directory inside the container to be created to the directory containing our app'
    working_dir: /mailer_service
    # mapping our project files to the app' folder within the container
    volumes:
      - ./:/mailer_service
Enter fullscreen mode Exit fullscreen mode

Now, onto writing the Dockerfile that is referenced above, we'll call it mailer_service.Dockerfile and it will live at the root of our project:

FROM php:8.1.2-fpm
RUN apt update && apt upgrade -y && apt install -y mailutils msmtp msmtp-mta
COPY ./msmtprc /etc/msmtprc
Enter fullscreen mode Exit fullscreen mode

In this Dockerfile, from a PHP-FPM base image (since we're going to use PHP to send emails programmatically), we're installing msmtp and msmtp-mta packages. mailutils is the package that will allow us to use the mail command line tool.

An MTA is a message transfer agent that acts as a relay between a computer and an SMTP server. Since this is exactly what we want to do, we need this package. Moreover, installing msmtp-mta will create a symlink inside our container to /usr/bin/sendmail so the container OS will know that it will use msmtp to send emails.

You'll notice that we're also copying an msmtp file from our host machine to /etc/msmtprc in our container. The /etc/msmtprc is a global configuration file that we'll need to create and fill so msmtp is aware of what account to use to send emails. Here is an example taylored for Gmail =>

# /etc/msmtprc is where that file will live inside the container

# this means that these will be the default settings used in all accounts
defaults

auth on
tls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
logfile ~/.msmtp.log

# example with a gmail address
account gmail
host smtp.gmail.com
port 587
from me@gmail.com
user me@gmail.com
password pwd_from_application_passwords_in_google_security_settings

# defines the default account
account default : gmail
Enter fullscreen mode Exit fullscreen mode

let's try this !

Run your monoservice (so far) application stack with a docker compose up. Once it's up, open a bash shell in your mailer service container; in my case I ran docker exec -it mailer_service_demo-mailer-service-1 bash.

Now, let's try to see if we receive an email by running mail -s "test subject" one_of_your_addresses@domain.com < /dev/null. This will send an empty email with only the subject. As a side note, you can send the contents of a text file by replacing the /dev/null part by the path of the text file that contains your email contents.

Yay ! it works, you should have received an email in your inbox 📧 . We're ready to use code now, in our case PHP code, to send an email.

demo PHP app'

Since I was using a PHP-FPM image, let's use a common service deployment using nginx. The idea here is to build a simple web app' that will send an email on hitting an /email-me endpoint.

To serve our app' running in our PHP-FPM container with nginx, we'll need to add a basic nginx configuration, let's create a file called nginx.conf at the root of our project:

server {

    listen 80;

    # dont forget to update your host machine hosts file
    server_name mailer_service.local;

    location /email-me {
        fastcgi_pass mailer_service:9000;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME /mailer_service/index.php;
    }

}
Enter fullscreen mode Exit fullscreen mode

In there we state that our server name will be mailer_service.local, so you should add the line containing 127.0.0.1 mailer_service.local in the hosts file of your machine so http://mailer_service.local can be resolved and picked up by nginx later when you'll try the app' in your browser.
We also route requests to /email-me to our PHP app' running in the mailer_service container.

You'll notice that we are referencing an index.php file in our nginx conf. Still at the root of the project, let's create this file

<?php
// ! replace the recipient email address here
mail(
    "one_of_your_addresses@domain.com", 
    "test subject", 
    "<p style='color:red;'>it works</p>", 
    "MIME-Version: 1.0" . "\r\n". "Content-type: text/html; charset=utf8" . "\r\n"
);

echo "email sent";
Enter fullscreen mode Exit fullscreen mode

This PHP file sends an email to an address of your choice, with some inline styles in it using HTML, thanks to the headers we pass as a 4th parameter of the built-in mail function.

Here are the modified contents of the previously created docker-compose.yml, required to put our server up:

services:

  # name of our service
  mailer_service:
    # giving to docker-compose the path of our custom Dockerfile that will define our service image
    build:
      context: .
      dockerfile: mailer_service.Dockerfile
    # setting the working directory inside the container to be created to the directory containing our app'
    working_dir: /mailer_service
    # mapping our project files to the app' folder within the container
    volumes:
      - ./:/mailer_service

  nginx-server:
    image: nginx:latest
    ports:
      - 80:80
    volumes:
      - ./:/mailer_service
      - ./nginx.conf:/etc/nginx/conf.d/nginx.conf
    # we specify that our nginx server depends on our PHP app'
    depends_on:
      - mailer_service
Enter fullscreen mode Exit fullscreen mode

Let's run the whole thing again with a docker compose up --build --force-recreate.

If all goes well, on visiting http://mailer_service.local/email-me, you should see the message email sent in your browser and, more importantly, you should have received an email (check out your spam if that's not the case).

Of course, this is not a very practical app', but the idea here was just to show how to wire everything together so you can get up and running quickly with a web app' that sends emails.

You can find the code of this example @ https://github.com/yactouat/mailer_service_demo.

One important note: DO NOT PUSH the msmtprc file to git as it contains a password to one of your Google's accounts 😬. What you can do is create an example file and version that one.

That's it from me, hope this helps !

Top comments (1)

Collapse
 
rginiarist profile image
RginiaRist

How Does Command Line Email Work? prayer to break up a couple