OCI Email, Micronaut, GraalVM Native Image

0
0
Send lab feedback

Send emails with Oracle Cloud Infrastructure (OCI) Email Delivery using the Micronaut framework

About this workshop

This workshop is for Java developers looking to build portable, cloud-native Java applications using Micronaut, GraalVM Native Image, and Email Delivery service in Oracle Cloud Infrastructure (OCI). In this lab, you'll learn how to send emails using OCI Email Delivery and Micronaut.

Lab Contents

In this lab, you will:

  • Connect VS Code to a remote development environment in an OCI Compute Instance
  • Review the application source code developed using the Micronaut framework
  • Configure OCI Email Delivery:
    • generate SMTP credentials
    • add an Approved Sender
    • use the SMTP Endpoint for your OCI region
  • Build a native executable for the application using GraalVM Native Image
  • Send emails from your application using OCI Email Delivery

Estimated lab time: 30-45 minutes

NOTES:

  • Whenever you see the laptop icon you'll need to perform an action such as entering a command or editing a file.

    # The box under the icon will tell you what to do.
  • To copy a command, hover over the field and then click the copy to clipboard icon.

  • To paste a copied command in a terminal window, right click and select the Paste option from the context menu. If you prefer keyboard shortcuts instead, use CTRL+SHIFT+V.

Introduction

What is Micronaut?

Micronaut is a modern, JVM-based framework to build modular, easily testable microservice and serverless applications. By avoiding runtime reflection in favor of annotation processing, Micronaut improves the Java-based development experience by detecting errors at compile time instead of runtime, and improves Java-based application start time and memory footprint. Micronaut includes a persistence framework called Micronaut Data that precomputes your SQL queries at compilation time making it a great fit for working with databases like MySQL, Oracle Autonomous Database, etc.

What is GraalVM Native Image?

GraalVM is a high-performance JDK distribution that can accelerate any Java workload running on the HotSpot JVM. GraalVM Native Image ’s ahead-of-time compilation provides a whole new way to deploy Java applications ideal for containerization. At build time, GraalVM Native Image analyzes a Java application and its dependencies to identify just what classes, methods, and fields are absolutely necessary and generates optimized machine code for just those elements.

Micronaut uses GraalVM Native Image to build lightweight Java applications that use less memory and CPUs, are smaller and faster because of an advanced ahead-of-time compilation technology.

  GraalVM Enterprise is available at no additional cost on Oracle Cloud Infrastructure (OCI)

STEP 1: Connect VS Code to a remote OCI Compute Instance

Your development environment is provided on a remote OCI Compute Instance. In this section, you will connect VS Code from the Luna Desktop environment to the remote machine by running a setup script.

  1. Double-click the Luna Lab icon on the desktop to open the browser.

  2. After the required OCI network and compute resources are provisioned (usually in 2-3 minutes), the animated gear beside Resources turns into a checkmark, and you can proceed with the lab.

  3. Click Resources.

  4. Scroll down to Configure and copy the text from the STEP 1.4 - Open VSCode and Connect text box. You many need to click on View Details. This script will setup the environment needed for the lab and launch VS Code. You can use the Copy to clipboard button on the far right that appears when you hover over the box.

  5. Click the Applications menu and open a Terminal Emulator.

  6. Place your cursor in the Terminal window and paste the lab init script you copied (Shift+Ctrl+V). A dialog box will warn you that you're pasting multiple lines which looks suspicious, but click Paste to proceed.

  7. A VS Code window will open and automatically connect to the remote VM instance that has been provisioned for you. Click Continue to accept the machine fingerprint.

  8. The green box at the lower left corner of VS Code will display SSH: <REMOTE VM IP ADDRESS> to indicate you are connected over SSH.

Congratulations, your VS Code has successfully connected to a remote host in Oracle Cloud Infrastructure!

STEP 2: Review the sample application source code

In this section, we will review the sample Micronaut application code used in this lab. The application source code and build scripts are available in the Lab folder in VS Code.

Let's go through the main components of the application.

2.1 Dependencies

We've added three dependencies to the build to add email support:

  • micronaut-email-javamail is mandatory
  • micronaut-email-template and micronaut-views-thymeleaf are optional. We've included these two libraries because we are using email templates in this example.

pom.xml

<dependency>
    <groupId>io.micronaut.email</groupId>
    <artifactId>micronaut-email-javamail</artifactId>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>io.micronaut.email</groupId>
    <artifactId>micronaut-email-template</artifactId>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>io.micronaut.views</groupId>
    <artifactId>micronaut-views-thymeleaf</artifactId>
    <scope>compile</scope>
</dependency>

2.2 Email sender configuration

We've added the following configuration snippet to application.yml. By using placeholder variables like FROM_EMAIL and FROM_NAME in application.yml, we can externalize the values via environment variables and avoid hard-coding.

src/main/resources/application.yml

micronaut:
  email:
    from:
      email: ${FROM_EMAIL:`default@test.com`} # <1>
      name: ${FROM_NAME:'Default Sender'} # <2>
  1. Sender’s email
  2. Sender’s name

Note: This way property: ${<placeholder>:<default-value>} of defining configuration enables us to externalize the configuration easily. For example, if a configuration placeholder (<placeholder>) like FROM_NAME is not defined, the Micronaut framework will use the default value (<default-value>) specified. So if FROM_NAME is not specified, the Micronaut framework sets the value of name to Default Sender.

2.3 SMTP configuration

We've added the following snippet to application.yml to supply the SMTP credentials. This approach lets us externalize the values via environment variables or secure storage such as OCI Vault.

src/main/resources/application.yml

smtp:
  password: ${SMTP_PASSWORD:'default-pass'} # <1>
  user: ${SMTP_USER:'default-user'} # <2>
  1. the SMTP password
  2. the SMTP username

2.4 JavaMail properties configuration

We've added the following snippet to application.yml to supply JavaMail properties:

src/main/resources/application.yml

javamail:
  properties:
    mail:
      smtp:
        port: 587
        auth: true
        starttls:
          enable: true
        host: ${SMTP_HOST:'default-smtp.com'} # <1>
  1. the SMTP server

2.5 Session Provider

Micronaut Email requires a bean of type SessionProvider when using JavaMail to create a Session. We've created the OciSessionProvider class for this.

src/main/java/example/micronaut/OciSessionProvider.java

  1. Use jakarta.inject.Singleton to designate a class as a singleton.

  1. Annotate a constructor parameter with @Property to inject a configuration value. We've injected SMTP configuration via constructor paramters annotated with @Property. We could have used a POJO annotated with @ConfigurationProperties as well.

  1. Use the user and password to create the Session.

2.6 Controller

We've created a controller EmailController that uses the Micronaut EmailSender to send emails.

src/main/java/example/micronaut/EmailController.java

  1. It is critical that any blocking I/O operations (such as fetching the data from the database) are offloaded to a separate thread pool that does not block the Event loop.

  2. The class is defined as a controller with the @Controller annotation mapped to the path /email.

  1. Use constructor injection to inject a bean of type EmailSender.

  1. By default, a Micronaut response uses application/json as Content-Type. We are returning a String, not a JSON object, so we set it to text/plain.

  2. We're sending a plain-text email.

  1. To send an HTML email, we're leveraging Micronaut's template rendering capabilities.

  1. A Micronaut controller action consumes application/json by default. Consuming other content types is supported with the @Consumes annotation or the consumes member of any HTTP method annotation.

  2. We're sending an email with an attachment.

STEP 3: Configure OCI Email Delivery

In this section, you will configure OCI Email Delivery.

3.1 Create SMTP credentials

  1. Go to the Luna Lab page opened in the browser, and click Resources.

  2. Scroll down to Configure and copy the text from the STEP 3.1 - Create SMTP credentials text box. You many need to click on View Details. You can use the Copy to clipboard button on the far right that appears when you hover over the box.

  3. Open a new terminal in VS Code using the Terminal>New Terminal menu and paste (Shift+Ctrl+V) the copied script.

    The response should look like this:

    {
      "data": {
       "description": "mn-email-user smtp credentials",
       "id": "ocid1.credential.oc1..aaaaaaaal...",
       "lifecycle-state": "ACTIVE",
       "password": "nB$O.......",
       "user-id": "ocid1.user.oc1..aaaaaaaaqx...",
       "username": "ocid1.user.oc1..aaaa....me.com"
      }
    }
  4. Set the value of password from the response in the environment variable SMTP_PASSWORD.

    Replace nB$O....... with the actual value. Enclose the value in single quotes ' ' (instead of double quotes " ") as shown below to handle special characters.

    export SMTP_PASSWORD='nB$O;.......'

    Check the value set by running the following command:

    echo $SMTP_PASSWORD
  5. Set the value of username from the response in the environment variable SMTP_USER.

    Replace ocid1.user.oc1..aaaa....me.com with the actual value. Enclose the value in single quotes ' ' (instead of double quotes " ") as shown below to handle special characters.

    export SMTP_USER='ocid1.user.oc1..aaaa....me.com'

    Check the value set by running the following command:

    echo $SMTP_USER

Note: In this example, we've used environment variables to store the SMTP user and password values for simplicity. Note that storing secrets in a vault provides greater security than you might achieve by storing secrets elsewhere, such as in configuration files or environment variables.

3.2 Create an approved sender

  1. Go to the Luna Lab page opened in the browser, and click Resources.

  2. Scroll down to Configure and copy the text from the STEP 3.2 - Create an approved sender text box. You many need to click on View Details. You can use the Copy to clipboard button on the far right that appears when you hover over the box.

  3. In the same terminal in VS Code, paste (Shift+Ctrl+V) the copied script.

    This will create an approved email sender in your lab compartment.

3.3 Set email configuration environment variables

  1. Go to the Luna Lab page opened in the browser, and click Resources.

  2. Scroll down to Configure and copy the text from the STEP 3.3 - Set email configuration environment variables text box. You many need to click on View Details. You can use the Copy to clipboard button on the far right that appears when you hover over the box.

  3. In the same terminal in VS Code, paste (Shift+Ctrl+V) the copied script. A dialog box will ask you Are you sure you want to paste 4 lines of test in to the terminal?. Check the Do not ask me again checkbox and press Paste to proceed.

    This will create three environment variables FROM_EMAIL, FROM_NAME and SMTP_HOST.

3.4 Change the "to" email address

In this step, you'll change the "to" email address to your personal email address so that you can verify the emails sent by the application in subsequent steps.

  1. Go to EmailController.java and replace recipient@domain.com with your personal email address. Specify a valid personal email address where you can see the emails sent by this application in the next section.

    src/main/java/example/micronaut/EmailController.java

    class EmailController { 
       ...
       private final String toEmail = "recipient@domain.com";
       ...
  2. Save the file.

Congratulations! In this section, you configured the application to use OCI Email Delivery. In the next section, you'll learn how to build and run the application code and send emails.

STEP 4: Build and run the sample application code

In this section, you'll build and run the application code and send emails.

  1. In the same terminal in VS Code, run the ./mvnw mn:run command, which builds and starts the application on port 8080. Let's run it in the background by adding an &.

    ./mvnw mn:run &
  2. Send a simple plain-text email:

    curl -X POST localhost:8080/email/basic

    Check your email. You should see the basic email in your Inbox or Spam folder.

  3. Send a templated email:

    curl -X POST localhost:8080/email/template/test

    Check your email. You should see the template email in your Inbox or Spam folder.

  4. Send an email with an attachment.

    curl -X POST \
       -H "Content-Type: multipart/form-data" \
       -F "file=@ README.md" \
       localhost:8080/email/attachment

    Check your email. You should see the attachment email in your Inbox or Spam folder.

  5. Bring the running application in the foreground:

    fg
  6. Once the application is running in the foreground, press CTRL+C to stop it.

Congratulations! Your Micronaut application is now using OCI Email Delivery to send emails. In the next section, you'll learn how to generate a native executable and send emails using the native executable.

STEP 5: Generate a native executable for the application

In this section, you'll generate a native executable for the application and run the native executable to send emails.

For this, you will use GraalVM Native Image ’s ahead-of-time compilation to generate a native executable for our sample application.

At build time, GraalVM Native Image analyzes a Java application and its dependencies to identify just what classes, methods, and fields are absolutely necessary, and generates optimized machine code for just these elements.

Compiling applications ahead of time with GraalVM Native Image improves the startup time and reduces the memory footprint of JVM-based applications.

  1. In the same terminal in VS Code, check the version of the GraalVM Enterprise native-image utility:

    native-image --version
  2. To generate a native executable using Maven, run:

    ./mvnw package -Dpackaging=native-image

    It can take ~3-4 minutes to generate the native executable.

  3. The native executable is created in the target directory and can be run with target/micronautguide. Let's run it in the background by adding an &.

    The native executable starts in just ~15 ms.

    target/micronautguide &
  4. Send a simple plain-text email:

    curl -X POST localhost:8080/email/basic

    Check your email. You should see the basic email in your Inbox or Spam folder.

  5. Send a templated email:

    curl -X POST localhost:8080/email/template/test

    Check your email. You should see the template email in your Inbox or Spam folder.

  6. Send an email with an attachment.

    curl -X POST \
       -H "Content-Type: multipart/form-data" \
       -F "file=@ README.md" \
       localhost:8080/email/attachment

    Check your email. You should see the attachment email in your Inbox or Spam folder.

  7. Bring the running application in the foreground:

    fg
  8. Once the application is running in the foreground, press CTRL+C to stop it.

Congratulations! Your Micronaut application native executable is now using the OCI Email Delivery to send emails.

Conclusion

In this workshop, you've seen how we can build a portable cloud-native Java application using Micronaut, GraalVM Native Image, and OCI Email Delivery to send emails.

Similarly, with Micronaut and GraalVM, you can easily send emails using AWS SES.

Learn more

SSR