laravelazureauthenticationemailauthorization

sending emails with laravel 9 using azure


I use the azure-mailer for laravel, then I set up azure communication services and the email communcation service, I set up an azure subdomain instead of a custom one https://github.com/hafael/azure-mailer-driver

note down the access-key and endpoint and put them in the .env file.

MAIL_MAILER=azure

AZURE_MAIL_RESOURCE_NAME=lab_rg
AZURE_MAIL_ENDPOINT={my-endpoint}
AZURE_MAIL_KEY={my-key}
# AZURE_MAIL_API_VERSION=2023-03-31 #optional
# AZURE_MAIL_DISABLE_TRACKING=false #optional

insert the config to mail.php, as the repository README states:

'mailers' => [
        //...other drivers

        'azure' => [
            'transport'             => 'azure',
            'resource_name'         => env('AZURE_MAIL_RESOURCE_NAME'),
            'endpoint'              => env('AZURE_MAIL_ENDPOINT', 'https://my-acs-resource-name.communication.azure.com'),
            'access_key'            => env('AZURE_MAIL_KEY'),
            'api_version'           => env('AZURE_MAIL_API_VERSION', '2023-03-31'),
            'disable_user_tracking' => env('AZURE_MAIL_DISABLE_TRACKING', false),
        ],
    ]

made a simple emailClient and test out

<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;

class AzureTestMail extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Get the message envelope.
     *
     * @return \Illuminate\Mail\Mailables\Envelope
     */
    public function envelope()
    {
        return new Envelope(
            subject: 'Azure Test Mail',
        );
    }

    /**
     * Get the message content definition.
     *
     * @return \Illuminate\Mail\Mailables\Content
     */
    public function content()
    {
        return new Content(
            view: 'emails.test',
        );
    }

    /**
     * Get the attachments for the message.
     *
     * @return array
     */
    public function attachments()
    {
        return [];
    }
}
Route::get('/mail', function () {
    Mail::to('receiver@gmail.com')->send(new AzureTestMail());
    return 'Email sent!';
});

Got a permission denied, request just failed

so I tried to see if a simple POST request could work on this:

{endpointURL}/emails:send?api-version=2023-03-31

I filled out the headers, content-type: application/json authorization: {access-key}

{
  "senderAddress": "DoNotReply@6fb023d1-8e97-405d-917c-54f74d97d0bf.azurecomm.net",
  "content": {
    "subject": "Test Email from Postman",
    "plainText": "This is a plain text email sent from Postman."
  },
  "recipients": {
    "to": [
      {
        "address": "receiver@gmail.com",
        "displayName": "Recipient Name"
      }
    ]
  }
}

still denied, went around searching for answers, maybe the token is just wrong, so I use this command:

az communication identity token issue --scope chat voip --connection-string {my-connection-string-I-found-in-communication-services}

got token and replace authorization value in header, still denied

if I were to try out the email in azure, it would work, so it got to be something about the authentication that my resource provider just kept denying me. azure try email section

so I tried: -replacing tokens -checking keys

no work


Solution

  • The issue is related to authentication and permissions while sending emails using Azure Communication Services with Laravel 9.

    Below is the code to send emails using Azure Communication Services:

    <?php
    require 'vendor/autoload.php';
    use GuzzleHttp\Client;
    use GuzzleHttp\Exception\RequestException;
    
    function computeContentHash($content)
    {
        
        return base64_encode(hash('sha256', $content, true));
    }
    
    function computeSignature($stringToSign, $secret)
    {
        
        $decodedSecret = base64_decode($secret);
        
        return base64_encode(hash_hmac('sha256', $stringToSign, $decodedSecret, true));
    }
    
    function sendEmail()
    {
        $resourceEndpoint = "https://ResourceName.communication.azure.com";
        $apiVersion = "2023-03-31";
        $requestUri = "$resourceEndpoint/emails:send?api-version=$apiVersion";
        
        $body = [
            'headers' => [
                'ClientCorrelationId' => '123',
                'ClientCustomHeaderName' => 'ClientCustomHeaderValue'
            ],
            'senderAddress' => 'DoNotReply@9.azurecomm.net',
            'content' => [
                'subject' => 'An exciting offer especially for you!',
                'plainText' => 'This exciting offer was created especially for you, our most loyal customer.',
                'html' => '<html><head><title>Exciting offer!</title></head><body><h1>This exciting offer was created especially for you, our most loyal customer.</h1></body></html>'
            ],
            'recipients' => [
                'to' => [
                    [
                        'address' => 'sampath80@gmail.com',
                        'displayName' => 'sam'
                    ]
                ],
                'cc' => [],
                'bcc' => []
            ],
            'attachments' => [
                [
                    'name' => 'MyAttachment.pdf',
                    'contentType' => 'application/pdf',
                    'contentInBase64' => 'TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQ='
                ]
            ],
            'replyTo' => [
                [
                    'address' => 'sampath80@gmail.com',
                    'displayName' => 'sam'
                ]
            ],
            'userEngagementTrackingDisabled' => true
        ];
    
        $serializedBody = json_encode($body);
    
        $date = gmdate('D, d M Y H:i:s T');
        echo "x-ms-date: $date\n";
    
        $contentHash = computeContentHash($serializedBody);
        echo "x-ms-content-sha256: $contentHash\n";
    
        $host = parse_url($resourceEndpoint, PHP_URL_HOST);
        $stringToSign = "POST\n/emails:send?api-version=$apiVersion\n$date;$host;$contentHash";
        echo "String to Sign: $stringToSign\n";
    
        $secretKey = "Primarykey";
        $signature = computeSignature($stringToSign, $secretKey);
    
        $authorizationHeader = "HMAC-SHA256 SignedHeaders=x-ms-date;host;x-ms-content-sha256&Signature=$signature";
        echo "Authorization Header: $authorizationHeader\n";
    
        try {
            $client = new Client([
                'verify' => false  
            ]);
            $response = $client->post($requestUri, [
                'headers' => [
                    'x-ms-date' => $date,
                    'x-ms-content-sha256' => $contentHash,
                    'Authorization' => $authorizationHeader,
                    'Content-Type' => 'application/json'
                ],
                'body' => $serializedBody
            ]);
    
            echo "Response: " . $response->getBody() . "\n";
        } catch (RequestException $e) {
            echo "Request failed: " . $e->getMessage() . "\n";
        }
    }
    sendEmail();
    

    composer.json

    "require": {
    
    "symfony/console": "^7.2",
    
    "guzzlehttp/guzzle": "^7.9"
    
    }
    

    Output: emails using Azure Communication Services

    emails using Azure Communication Services