Service Broker: Setup Email Notifications On Disabled Queues
It took me some time to figure our this part even though it should be trivial. So hopefully this guide will help someone in the future. I think the hardest part was to understand the security context under which internal stored procedures run when activated by service broker.
First thing you need to do is to create a DisabledQueueNotificationQueue. I know I know, it’s a little redundant name but what it means is that you have a queue that receives notifications when other queues get disabled, therefore redundant name.
CREATE QUEUE dbo.DisabledQueueNotificationQueue
Create service endpoint with PostEventNotification message type
CREATE SERVICE DisabledQueueNotificationService
ON QUEUE DisabledQueueNotificationQueue
(
[http://schemas.microsoft.com/SQL/Notifications/PostEventNotification]
)
GO
Next is we need to setup a notification procedure which will execute msdb.dbo.sp_send_mail
CREATE PROCEDURE [dbo].[spServiceBroker_SendDisabledQueueEmailNotification]
WITH EXECUTE AS OWNER
AS
DECLARE @ch UNIQUEIDENTIFIER
DECLARE @messagetypename NVARCHAR(256)
DECLARE @messagebody XML
DECLARE @queueName varchar(100)
DECLARE @emailTo VARCHAR(500)
SET @emailTo = 'someaddress@yahoo.com'
WHILE (1=1)
BEGIN
BEGIN TRY
BEGIN TRANSACTION
WAITFOR (
RECEIVE TOP(1)
@ch = conversation_handle,
@messagetypename = message_type_name,
@messagebody = CAST(message_body AS XML)
FROM DisabledQueueNotificationQueue
), TIMEOUT 60000
IF (@@ROWCOUNT = 0)
BEGIN
ROLLBACK TRANSACTION
BREAK
END
IF (@messagetypename = 'http://schemas.microsoft.com/SQL/Notifications/EventNotification')
BEGIN
SET @queueName = 'Disabled queue: ' + @messagebody.value('/EVENT_INSTANCE[1]/ObjectName[1]', 'VARCHAR(100)');
EXEC msdb.dbo.sp_send_dbmail
@profile_name = 'DBA_Notifications',
@recipients = @emailTo,
@body = @queueName,
@subject = @queueName;
END
IF (@messagetypename = 'http://schemas.microsoft.com/SQL/ServiceBroker/Error')
BEGIN
DECLARE @errorcode INT
DECLARE @errormessage NVARCHAR(3000)
-- Extract the error information from the sent message
SET @errorcode = (SELECT @messagebody.value(
N'declare namespace brokerns="http://schemas.microsoft.com/SQL/ServiceBroker/Error";
(/brokerns:Error/brokerns:Code)[1]', 'int'));
SET @errormessage = (SELECT @messagebody.value(
'declare namespace brokerns="http://schemas.microsoft.com/SQL/ServiceBroker/Error";
(/brokerns:Error/brokerns:Description)[1]', 'nvarchar(3000)'));
-- Log the error
-- End the conversation on the initiator's side
END CONVERSATION @ch;
END
IF (@messagetypename = 'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog')
BEGIN
-- End the conversation
END CONVERSATION @ch;
END
COMMIT TRANSACTION
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
DECLARE @ErrorNum INT
DECLARE @ErrorMsg NVARCHAR(3000)
SELECT @ErrorNum = ERROR_NUMBER(), @ErrorMsg = ERROR_MESSAGE()
-- log the error
BREAK
END CATCH
END
The important piece is that this procedure will execute as owner which will be a dbo schema when service broker activates the internal procedure.
Next we add the queue internal activation
ALTER QUEUE DisabledQueueNotificationQueue
WITH ACTIVATION
(
PROCEDURE_NAME = dbo.spServiceBroker_SendDisabledQueueEmailNotification,
STATUS = ON,
MAX_QUEUE_READERS = 1,
EXECUTE AS OWNER
)
Finally we create an event notification to send messages when queue gets disabled into the DisabledQueueNotifiactionQueue
CREATE EVENT NOTIFICATION DisabledTargetQueueNotification
ON QUEUE dbo.TargetQueue
FOR BROKER_QUEUE_DISABLED
TO SERVICE 'DisabledQueueNotificationService', 'current database';
If we try to simulate a poison message and rollback receive 5 times the procedure won’t execute and will log an error:
The EXECUTE permission was denied on the object ‘sp_send_dbmail’, database ‘msdb’, schema ‘dbo’.
This is expected since the activated stored procedure is calling an msdb database and has no permissions since it is in the public context. Since I trust other database on my sql server I thought that enabling trustworthy would fix this problem: ALTER DATABASE [msdb] SET TRUSTWORTHY ON
. But apparently the setup of my sql server would still not allow a public process to access system database. So the only one thing that was left to do is to create a certificate and sign the procedure so it would be trusted.
CREATE CERTIFICATE spServiceBroker_SendDisabledQueueEmailNotificationCertificate
ENCRYPTION BY PASSWORD = 'SommePass91'
WITH SUBJECT = 'spServiceBroker_SendDisabledQueueEmailNotification signing certificate'
GO
ADD SIGNATURE TO OBJECT::[spServiceBroker_SendDisabledQueueEmailNotification]
BY CERTIFICATE [spServiceBroker_SendDisabledQueueEmailNotificationCertificate]
WITH PASSWORD = 'SommePass91'
GO
--------------------------------------------------------------------------------
-- We leave the private key so we can resign the procedure later if it changes.
-- If we remove the certificate the whole thing will need to be recreated
--------------------------------------------------------------------------------
--ALTER CERTIFICATE spServiceBroker_SendDisabledQueueEmailNotificationCertificate
-- REMOVE PRIVATE KEY
--GO
BACKUP CERTIFICATE [spServiceBroker_SendDisabledQueueEmailNotificationCertificate]
TO FILE = 'c:\spServiceBroker_SendDisabledQueueEmailNotificationCertificate.cert'
GO
USE msdb
GO
CREATE CERTIFICATE [spServiceBroker_SendDisabledQueueEmailNotificationCertificate]
FROM FILE = 'c:\spServiceBroker_SendDisabledQueueEmailNotificationCertificate.cert'
GO
CREATE USER [spServiceBroker_SendDisabledQueueEmailNotificationUser]
FROM CERTIFICATE [spServiceBroker_SendDisabledQueueEmailNotificationCertificate]
GO
GRANT AUTHENTICATE TO [spServiceBroker_SendDisabledQueueEmailNotificationUser]
GRANT EXECUTE ON [sp_send_dbmail] TO [spServiceBroker_SendDisabledQueueEmailNotificationUser];
Now that the procedure has access to the sp_send_dbmail
we have no issues and if you simulate poison message, you should receive an email notification with the name of the queue.