How to send an email with PowerShell using SendGrid API

I really like event-driven notifications that can trigger different webhooks and it’s really fun putting them together like pieces of lego to automate workflows.

The most common and simple notification method is via email, but there are scenarios where environments for security reasons and by design have just access to the internet on port 80/443  and this connection is often mediated via a web proxy.

Not having access to SMTP protocol can be a roadblock but, in this article, we will implement a solution to send an email with PowerShell under these limitations without changing Firewall Rules or NSG.

How can we send an email without SMTP (or IMAP)?

SMTP protocol requires TCP port 25 or 587 (with TLS encryption) as the default port for mail submission if those ports are closed my obvious choice is using a web service that will require an API Key as authentication and authorization via web request on HTTPS (using port 443).

In this example, I used Twilio SendGrid (This is not a sponsored article).  It’s very popular and the free tier for this application is probably enough for most testing applications or scenarios.

Once you’ve signed up and created your profile you can click on Settings, API KEYS (https://app.sendgrid.com/settings/api_keys) and CREATE API KEY.

My Personal TIP:  Remember that API KEYs are free, generate one for each of your apps, so it can be considered as a unique identifier so if it gets compromised it’s possible to identify it and to delete it without causing any interruption for other apps using/sharing the same service or account.

Once created, you’ve got everything you need to start using this script.

Send-MailWithSendGrid

I’ve created a PowerShell function that is a wrapper for Invoke-RestMethod and posts the JSON containing the email message the API Key that SendGrid is expecting to receive.

To use this function you can simply import with dot sourcing

Replace these variables with your test values

And finally, run this one-liner

Take advantage of Splatting

Oliver in one of the comments below has pointed out the use of parameter splatting. Indeed, if you have a long list of parameters to pass to a cmd-let or a function in genera is to pack all your variables into a hast table and pass that object to the function.

There are multiple benefits of splatting,  it will not just make your code more readable, but all parameters will be stored into a single object ( hash table),  that will be easy to manipulate, count or iterate if needed.

If you require a proxy

As I’ve mentioned this function is a wrapper of Invoke-RestMethod so it’s simple to check the documentation and for instance adding the proxy address to the Invoke-RestMethod cmd-let.

Wrap-Up

I still think that email communication can be still used effectively today and the choice of having other valid alternatives, for instance, Teams/Slack , can help us to make the right decision when it is okay to use it or simply avoid abusing it.
When we decide that the email is the right solution even when SMTP protocol is not available we can replace the cmd-let of Send-MailMessage with this Send-EmailWithSendGrid using the SendGrid RESTAPI.
As usual, you can find this script on my GitHub Repository.

12 Replies to “How to send an email with PowerShell using SendGrid API”

  1. Hello Paolo,
    can I suggest the use of splatting to pass the parameters to your function
    i.e.
    $MailParams = @{
    From = “[email protected]
    To = “[email protected]
    APIKEY = “MY_API_KEY”
    Subject = “TEST”
    Body =”SENDGRID 123″
    }
    Send-EmailWithSendGrid @MailParams

    The commandline is shoter, and more readable and easily customizable.

    Regards
    Olivier

    1. Hi Oliver,
      Thanks for your comment, yes splatting can make the line for the cmd-let shorter, as much as I like hash tables and I wanted to make the code even simpler.
      I will edit this post and include your comment, I didn’t think to include splatting in this, but, in general, you’re right and it’s a nice option to have all parameters into a single hash table object if you want for instance iterate or manipulate it.

    1. Hi Spizzy,
      Thanks for your comment. According to the official documentation of SendGrid https://sendgrid.com/docs/api-reference/ you can send attachments(an array of objects) but you need to convert your file with base64 into a string the attachment yourself you cannot pass the absolute path of the file.
      Each attachment in the array will be a set of 2 required strings filename and content.
      I suggest breaking this into a separate helper function where you pass a filename as an argument and it returns 2 strings (or a hash table) filename and content. So you can keep your code still close to a one-liner.

      I will publish an article tomorrow on how to encode in base64 using .NET framework, but I’ve already pasted in my github repository the code if you want to have a look: https://github.com/PaoloFrigo/scriptinglibrary/blob/master/Blog/PowerShell/Encode-FileToBase64.ps1

      I hope that I have answered your question. Regards

      1. Hey Paolo, this helps. That part im missing is what the json request with the attachment part added in. json is totally new to me and the structure is strange. how would the attachment in the json structure look?

        thanks, sir.

        1. Hi Spizzy,
          I strongly encourage you to get familiar with XML, JSON, YAML. It doesn’t matter what role you’re in right now, you’ll soon need to use them and know pros and cons of all of these especially when you are the developer and you need to choose which one to implement.

          Have you read this article on how to convert a file into a base64 string?
          https://www.scriptinglibrary.com/languages/powershell/base64-encoding-with-powershell-and-net-framework/

          My script above doesn’t require an attachment, but on the JSON request add the previous article and this code, it should be a matter of just joining all the pieces together.


          attachments: [
          {
          content: "attachment_converted_into_base64string",
          filename: "name_of_your_attachment_with_extension",

          }
          ]

          Experiment and let me know if you need help.

          Regards

    2. Use the below code

      $attachmentContent = @()
      foreach($file in $AttachmentFileArray)
      {
      $attachmentContent += New-Object PSObject -Property @{
      ‘content’ = [Convert]::ToBase64String([IO.File]::ReadAllBytes($file))
      ‘filename’ = $file.Split(‘\’)[-1]
      }
      }

  2. I have been trying to send to multiple recipients using Splatting but getting the below error, Please help.

    To = “[email protected]” , “[email protected]
    ——————————————————————–
    Send-EmailWithSendGrid : Cannot process argument transformation on parameter ‘To’. Cannot convert value to type System.String.
    At line:82 char:24
    + Send-EmailWithSendGrid @MailParams
    + ~~~~~~~~~~~
    + CategoryInfo : InvalidData: (:) [Send-EmailWithSendGrid], ParameterBindingArgumentTransformationException
    + FullyQualifiedErrorId : ParameterArgumentTransformationError,Send-EmailWithSendGrid

    1. Hi Ayush,
      Thanks for your comment. If you look at the beginning of the Send-EmailWithSendGrid function definition you will notice that To is not an Array or List of Strings, but a simple String object. So the error you are getting is espected.

      If you want to pass an array of strings as mentioned in your comment changing that function is the first change needed.

      The second step and most important one is consulting the SendGrid API to double-check if that change is allowed:
      https://sendgrid.com/docs/API_Reference/Web_API_v3/Mail/index.html
      Ideally, knowing that function depends on this API this research should be done upfront to avoid wasting time.
      Yes, this is possible, the only limitation is that the size of that array must be less than 1000, so I suggest to validate the size of the To array and making your function a little bit smarter.

      This is what I’m referring to:

      The total number of recipients must be less than 1000. This includes all recipients defined within the to, cc, and bcc parameters, across each object that you include in the personalizations array.

      If you are not successful in your attempts, feel free to reach out.

      Another option (more like a workaround) if you want to send the same email to your list of recipients, you can call the same function sending it out one user at a time by simply using a for loop.
      This would be like sending individual emails, instead of an email with the same ‘to’ list of addresses in the mail header. In your comment I guess you want all users to know each other address and can reply-to-all, instead of in this workaround method each one will receive an email with one single address in the ‘to’ field.
      It may work for you anyway, but it depends on the result you want to achieve.

      Regards

  3. Hi Paolo,

    Thanks for your response.

    I am very new to PowerShell, I have tried to change the string type of $To to array of string but no lock. I am just trying to send emails to multiple recipients and will definitely less than 1000 recipients, not individually but to everyone added to the $To list. Also I have tried to add cc and bcc to the function it didn’t work.

    Can you please suggest what changes you are referring to in the above reply for changing the function which will allow me to send emails to multiple recipients. Also if you can suggest me how to add cc and bcc into the function.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.