Send Emails with SendGrid API using Micronaut with Kotlin Coroutines

Ubed Ali

What is Coroutine?

From Kotlin docs:

One can think of a coroutine as a light-weight thread. Like threads, coroutines can run in parallel, wait for each other and communicate. The biggest difference is that coroutines are very cheap, almost free: we can create thousands of them, and pay very little in terms of performance. True threads, on the other hand, are expensive to start and keep around. A thousand threads can be a serious challenge for a modern machine.

SEO stands for Search Engine Optimisation, which is the practice of optimising your website and increasing the quantity and quality of traffic to your website through organic search engine results.

So why Kotlin Coroutines?

A coroutine is an instance of suspendable computation. It is conceptually similar to a thread, in the sense that it takes a block of code to run that works concurrently with the rest of the code. However, a coroutine is not bound to any particular thread. It may suspend its execution in one thread and resume in another one.

  • Coroutines help to manage long-running tasks that might otherwise block the main thread and cause your app to become unresponsive.

  • They are light-weight

You’re expected to be familiar with basic Kotlin syntax, but prior knowledge of coroutines is not required.

Coroutines basics

This section covers basic coroutine concepts.

Your first Coroutine

Coroutines can be thought of as light-weight threads, but there is a number of important differences that make their real-life usage very different from threads.

Run the following code to get to your first working coroutine:

You will see the following result:


You can get the full code here


Let’s dissect what this code does.

launch is a coroutine builder. It launches a new coroutine concurrently with the rest of the code, which continues to work independently. That’s why Hello has been printed first.

delay is a special suspending function. It suspends the coroutine for a specific time.

Suspending a coroutine does not block the underlying thread, but allows other coroutines to run and use the underlying thread for their code.

runBlocking is also a coroutine builder that bridges the non-coroutine world of a regular fun main() and the code with coroutines inside of runBlocking { … }. This is highlighted in an IDE by this: CoroutineScope hint right after the runBlocking opening curly brace.

If you remove or forget runBlocking in this code, you’ll get an error on the launch call, since launch is declared only in the CoroutineScope:

Unresolved reference: launch

The name of runBlocking means that the thread that runs it (in this case — the main thread) gets blocked for the duration of the call, until all the coroutines inside runBlocking { … } complete their execution. You will often see runBlocking used like that at the very top-level of the application and quite rarely inside the real code, as threads are expensive resources and blocking them is inefficient and is often not desired.

Scope builder

In addition to the coroutine scope provided by different builders, it is possible to declare your own scope using the coroutineScope builder. It creates a coroutine scope and does not complete until all launched children complete.

runBlocking and coroutineScope builders may look similar because they both wait for their body and all its children to complete. The main difference is that the runBlocking method blocks the current thread for waiting, while coroutineScope just suspends, releasing the underlying thread for other usages. Because of that difference, runBlocking is a regular function and coroutineScope is a suspending function.

You can use coroutineScope from any suspending function. For example, you can move the concurrent printing of Hello and World into a suspend fun doWorld() function:

This code also prints:


An Explicit Job

A launch coroutine builder returns a Job object that is a handle to the launched coroutine and can be used to explicitly wait for its completion. For example, you can wait for completion of the child coroutine and then print “Done” string:

This code produces:



We have already come across suspend keywords many times. Let’s go deep into what suspending functions are!

suspend is a keyword in kotlin which indicates function can be paused and resumed later point in time. You can use suspending functions to call long-running computations without blocking the main thread.

Rules for calling suspending functions:

  • from other suspending functions.

  • from coroutine (suspending functions inherits coroutine context from coroutine from where it is invoked).

Suspend-Kotlin Coroutines

SUSPENDING: Function A, while has started, could be suspended, and let Function B execute, then only resume later. The thread is not locked by Function A.


To learn more about Coroutines do watch :

The best source to understand this is the talk “Deep Dive into Coroutines” by Roman Elizarov.

Asynchronous Programming with Kotlin” workshop by Roman Elizarov at KotlinConf.


How to Send Emails with SendGrid?

SendGrid is a transactional email service. SendGrid is responsible for sending billions of emails for some of the best and brightest companies in the world.

SendGrid API key

To use the Micronaut Sendgrid integration, we need an API Key.

  • Sign up for SendGrid.

Once we have created our account, the first thing we will see is web console:

Before we start, we need to create an API KEY , in order to use the services.

  • Head to the Settings > API key > Create API key.

Here we can create the key with its name and the permissions that we want to give it, all the permissions are by default, but if we click on the Restricted Access option we can modify the necessary permissions.

  • Store generated API key somewhere, as we’ll need it later in our app.

We must also add at least one email address from which the emails will be sent, for this we go to Settings > Sender Authentication , if it is the first time we access a blue button will appear in the upper right corner to create a new sender, if not, a white button will appear on the left with the text Verify Single Sender , that will take us to where the blue button is located to create a new sender, we click on it and a form will open, we fill it out, We verify the email, with the mail they send us, and we already have our first Sender added.

Once this is done, we go to the code, first of all we will add dependency




Create a MailController class. This class uses a collaborator, emailSender, to send an email.

You can send emails asynchronously using the AysnEmailSender API or synchronously using the EmailSender API.


  • When we enter into the main function it’ll first print the thread name, which is ‘main’ .
  • Then we are using sleep for 10 secs and pretend it’s doing some operations.
  • After 10 secs it will print ‘Exiting from ‘main’ thread’.

Let’s move to the Controller, create a new controller class named MailController,


  • The class is defined as a controller with the @Controller annotation mapped to the path ‘/mail/send/1’ and ‘/mail/send/2’ marked with suspend modifier.

Functions marked with the suspend keyword are transformed at compile time to be made asynchronous under the hood (in bytecode), even though they appear synchronous in the source code.

  • Use constructor injection to inject a bean of type AsyncEmailSender.
  • There are 2 @Post annotations that map the send method to HTTP POST requests on ‘/mail/send/1’ and ‘/send/mail/2’.
  • Return 202 ACCEPTED as the result if the email delivery succeeds
  • In ‘/send/1’ we are using sleep for 7 seconds, pretending it’s doing some operations in that time.
  • And in ‘/send/2’ we are using sleep for 1 sec, pretending it’s doing some operations too.

We are having 2 POST methods in our Controller class. Both the classes has been marked with the suspend modifier.


If you want to send every email with the same address, you can set it via configuration:


Running the Application

  • API Key

Set the SendGrid API Key as an environment variable before you run the application:

$ export SENDGRID_API_KEY='yourApiKey'
  • Run

To run the application, use the ‘./gradlew run’ command, which starts the application on port 8080 by default.

If it shows port *.8080 is in use

Head to ‘source/main/kotlin/resources/application.yml’ & change the port.

Here our application is up and running:

user@user:~/Desktop/email-app$ ./gradlew run
> Task :run
Entering into 'main' thread
 __  __ _                                  _   
|  \/  (_) ___ _ __ ___  _ __   __ _ _   _| |_ 
| |\/| | |/ __| '__/ _ \| '_ \ / _` | | | | __|
| |  | | | (__| | | (_) | | | | (_| | |_| | |_ 
|_|  |_|_|\___|_|  \___/|_| |_|\__,_|\__,_|\__|
  Micronaut (v3.6.1)
02:27:57.053 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 1644ms. Server Running: http://localhost:8081
Sending e-mail...
POST /send/2 is working on 'default-nioEventLoopGroup-3-5' thread
Exiting from 'main' thread
Sending e-mail...
POST /send/1 is working on 'default-nioEventLoopGroup-3-3' thread

In the terminal we can see

  • First it returns “Entering into ‘main’ thread”, then it will keep doing its operation in the background.

Asynchronously our ‘MailController’ class starts and returns ‘Sending e-mail…’.

  • Our “POST /send/2 is working on ‘default-nioEventLoopGroup-3–5’ thread”
  • Then ‘main’ fun finished its operations and printed ‘Exiting from main thread’.

As we want to run all the functions asynchronously using Kotlin Coroutines, we’ll send both /mail/send/1 and /mail/send/2 requests at the same time. But due to their difference in operations or we can say in their sleep time /send/mail/2 sends email before /send/mail/1 does.

  • Our “POST /send/1’ is working on ‘default-nioEventLoopGroup-3–3’ thread”.

Here we can see both of our POST methods are working on different threads and not using or blocking the main thread.


In this blog, we covered about kotlin coroutines and how to send email using SendGrid with Micronaut and kotlin coroutines . For complete source code check out our Github Repository here.

At Scalereal We believe in Sharing and Open Source.

So, If you found this helpful please give some claps 👏 and share it with everyone.

Sharing is Caring!

Thank you ;)