how I can resend all failed emails (msdb.dbo.sp_send_dbmail) - sql

I am sending emails using msdb.dbo.sp_send_dbmail.
Sometimes the emails do not send. I have got the list of emails which have failed.
SELECT TOP 10 * from msdb.dbo.sysmail_event_log
log_id event_type log_date process_id mailitem_id account_id last_mod_date last_mod_user
9022 error 50:15.9 5608 20428 NULL 50:15.9 sa
9023 error 51:23.3 5608 20428 NULL 51:23.3 sa
Now, I want to resend all failed emails again which are available in the sysmail_event_log table.
How can I resend all failed emails?

Use the following query for sending back a failed item.
Or use a CURSOR for each row from msdb.dbo.sysmail_faileditems with the same query
DECLARE #to varchar(max)
DECLARE #copy varchar(max)
DECLARE #title nvarchar(255)
DECLARE #msg nvarchar(max)
SELECT #to = recipients, #copy = copy_recipients, #title = [subject], #msg = body
FROM msdb.dbo.sysmail_faileditems
WHERE mailitem_id = 56299
EXEC msdb.dbo.sp_send_dbmail
#recipients = #to,
#copy_recipients = #copy,
#body = #msg,
#subject = #title,
#body_format = 'HTML';
References
resending-failed-emails-through-sp_send_email
CURSOR

Building from Hybris95's answer, here is a snippet that sends all failed items after a cutoff timestamp without a cursor. Mail profiles are taken in consideration.
DECLARE #MailitemId INT = 0
DECLARE #Cutoff DATE = CAST(GETDATE() AS DATE)
WHILE (1 = 1)
BEGIN
SELECT TOP 1 #MailitemId = mailitem_id
FROM msdb.dbo.sysmail_faileditems
WHERE
mailitem_id > #MailitemId
AND send_request_date > #Cutoff
ORDER BY mailitem_id
IF ##ROWCOUNT = 0 BREAK;
DECLARE #to VARCHAR(MAX)
DECLARE #copy VARCHAR(MAX)
DECLARE #title NVARCHAR(255)
DECLARE #msg NVARCHAR(MAX)
DECLARE #profile_name NVARCHAR(MAX),
#file_attachments NVARCHAR(MAX),
#attach_query_result_as_file BIT
SELECT #profile_name = p.name, #to = recipients, #copy = copy_recipients, #title = [subject], #msg = body, #file_attachments = i.file_attachments, #attach_query_result_as_file = i.attach_query_result_as_file
FROM msdb.dbo.sysmail_faileditems AS i LEFT OUTER JOIN msdb.dbo.sysmail_profile AS p
ON p.profile_id = i.profile_id
WHERE
mailitem_id = #MailitemId
EXEC msdb.dbo.sp_send_dbmail
#profile_name = #profile_name,
#recipients = #to,
#copy_recipients = #copy,
#body = #msg,
#subject = #title,
#body_format = 'HTML',
#file_attachments = #file_attachments,
#attach_query_result_as_file = #attach_query_result_as_file
END

Or you can do what send_dbmail itself does after building the message in source tables (which still exists in case of failed mail), and just push the failed message back onto the service broker send queue, and let it manage all the correct updates.
Declare #sendmailxml VARCHAR(max), #mailitem_id INT
-- Create the primary SSB xml maessage
SET #sendmailxml = '<requests:SendMail xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://schemas.microsoft.com/databasemail/requests RequestTypes.xsd" xmlns:requests="http://schemas.microsoft.com/databasemail/requests"><MailItemId>'
+ CONVERT(NVARCHAR(20), #mailitem_id) + N'</MailItemId></requests:SendMail>'
-- Send the send request on queue.
EXEC sp_SendMailQueues #sendmailxml
where #mailitem_id is the id of the message to resend that you have identified.
Just be aware the exact format of the XML Message is an undocumented internal, so may change by SQL version. The indented lines (including misspelled comment!) are pulled straight out of sp_send_dbmail on a SQL 17 server, and are same on 19. I believe they were different on some earlier version, but don't have installs to check.

Related

SQL Cursor and Email

I have a cursor in place that needs to send an email to one person containing all the rows associated with a certain field
To explain a bit further, i have a table which contains multiple rows, with one field containing the email address for all the rows associated with that email address.
I'm looking to send one email for that user with all the rows associated with that email address. At the moment, so if it has 4 rows, sends one email with all those rows. What its doing at the moment is sending 4 emails with all the rows associated with that email address
Essentially, just need it to send one email with all the rows for that email address
Thanks
Cursor loops through
Updates a table
Email then uses the variables to send the email
DECLARE #SUBJECT VARCHAR(100)
DECLARE #BODY VARCHAR(MAX)
DECLARE #EMAIL VARCHAR(60)
DECLARE #SUPERVISOREMAIL VARCHAR(60)
DECLARE #TABLEHTML VARCHAR(MAX)
DECLARE #TPROFILE VARCHAR(128)
DECLARE #ASSETREF VARCHAR(MAX)
DECLARE #PERSONREF VARCHAR(MAX)
DECLARE #USERNAME VARCHAR(MAX)
DECLARE #UPDATEDINVFIRE BIT
DECLARE #DATEUPDATEDINVFIRE DATETIME
DECLARE #DEVICEMODEL VARCHAR(MAX)
DECLARE #DESCRIPTION VARCHAR(MAX)
DECLARE CSR CURSOR
FOR SELECT ASSETREF, USERNAME, EMAIL, SUPERVISOREMAIL, UPDATEDINVFIRE, DATEUPDATEDINVFIRE FROM ##LEAVERUPDATE
OPEN CSR FETCH NEXT
FROM CSR INTO #ASSETREF, #USERNAME, #EMAIL, #SUPERVISOREMAIL, #UPDATEDINVFIRE, #DATEUPDATEDINVFIRE
WHILE ##FETCH_STATUS = 0
BEGIN
SET #SUBJECT = 'VFIRE DEVICE STATUS UPDATED FOR LEAVER'
SET #TABLEHTML = N'<P> The following user has had the following devices set to leaver status in vFire</P>' +
N'<P> Please be advised, these devices need to be returned immediately prior to the person leaving the business</P>' +
N'<table border="1">' +
N'<tr><th>CI Number</th><th>Username</th><th>Asset Model</th><th>Asset Description</th><th>Users Email Address</th><th>Supervisor EMail Address</th></tr>' +
CAST
((
SELECT
TD = ASSETREF, '',
TD = USERNAME, '',
TD = DEVICEMODEL, '',
TD = DESCRIPTION,'',
TD = EMAIL, '',
TD = SUPERVISOREMAIL,''
FROM ##LEAVERUPDATE
WHERE EMAIL = #EMAIL
FOR XML PATH('TR'), TYPE
) AS NVARCHAR(MAX) ) +
N'</TABLE>' +
N'<P> </P>';
EXEC msdb.dbo.sp_send_dbmail
#profile_name = #TPROFILE,
#recipients = #SUPERVISOREMAIL,
#copy_recipients='',
#blind_copy_recipients ='',
#subject = #SUBJECT,
#body = #TABLEHTML,
#body_format = 'HTML'
------------------------------------------
FETCH NEXT
FROM CSR INTO #ASSETREF, #USERNAME, #EMAIL, #SUPERVISOREMAIL, #UPDATEDINVFIRE, #DATEUPDATEDINVFIRE
END
CLOSE CSR
DEALLOCATE CSR

How to send an email only to users who appear in query results?

I am creating a query to return tickets that haven't been updated in the required time and send via email. I have everything working except I want to email only the users whose names appear in the query results.
I have tried setting the # user name = to user id and adding to the email as below. Not sure if this is even close.
Declare #username VARCHAR(MAX)
--("query")--
Set #username =[userid]--(in query results)--
exec msdb.dbo.sp_send_dbmail
#participants ='#username+#mydomain.com'
Expect to send emails to users whose user name appears in query results.
You can use STUFF with FOR XML PATH to create a ; delimited list for the #recipients parameter:
DECLARE #EmailList varchar(max)
SELECT #EmailList = STUFF((
SELECT ';' + Username + '#gmail.com'
FROM yourTable
FOR XML PATH('')
), 1, 1, '')
Then:
EXEC msdb.dbo.sp_send_dbmail
#recipients = #EmailList,
#subject = 'foo',
#body = 'bar',
etc
You can use Coalesce to create a list separated with semicolons which is what dbmail wants.
Declare #username varchar(max)
SELECT
#username = COALESCE(#username + '; ','') + userid + '#mydomain'
FROM
Yourtable
WHERE
Yourquery
EXEC msdb.dbo.sp_send_dbmail
#recipients = #username
#subject = 'BARK BARK'
#Body = 'BARK BARK BARK'
You can use a CURSOR to fetch your email address list and send the email to that list:
DECLARE #username AS VARCHAR(MAX) = ''
DECLARE #participants AS VARCHAR(MAX) = ''
DECLARE cursor_name CURSOR
FOR
SELECT userid
FROM YourTable
OPEN cursor_name;
FETCH NEXT
FROM cursor_name
INTO #username;
WHILE ##FETCH_STATUS = 0
BEGIN
#participants += #username + '#mydomain.com;'
FETCH NEXT FROM cursor_name
INTO #username;
END
CLOSE cursor_name;
DEALLOCATE cursor_name;
EXEC msdb.dbo.sp_send_dbmail #recipients = #participants
etc

Changed Sql-Server version, can't creat job with alert to send e-mail

In the database i manage, we have some internal dossiers that have a dead line, stablished by us on the date of creation. Now, i need to receive an e-mail, telling me which one have terminated.
I have created a simple job with code that would do it all, no go. I have created an alert, no go, always getting errors or, when not geting errors, nothing happens anyway.
select *
from bo
where nmdos like '%preço%'
and datafinal = DATEADD(day, -1, convert(date, GETDATE()))
i need to create an alert that, when the code returns the name and the client number on that dossier
You could create stored procedure to handle the query check, which would check if any rows are returned. If there are rows returned then send an email. This can be achieved by the below. Assuming you have dbsend mail configured in your SQL instance.
CREATE PROCEDURE dbo.DossierEmailSend
AS
DECLARE #rows int;
DECLARE #message varchar(1000);
SET #rows = (SELECT COUNT(*)
FROM bo
WHERE nmdos LIKE '%preço%'
AND datafinal = DATEADD(day, -1, CONVERT(date, GETDATE()))
)
SET NOCOUNT ON
SET #message = '<HTML>As at ' + CONVERT(char(19),GETDATE(),120) + '<BR><BR>DOSSIER FOUND'
IF #rows > 0
BEGIN
EXEC dbo.uspSendEmail 'FOUND DOSSIER', 'YOUR EMAIL', #message, NULL, 'CC EMAIL 1; CC EMAIL 2'
SET NOCOUNT OFF
END
You can then create a job within SQL Agent that will execute the check on whatever frequency you require.
If you are missing the send mail sp you can create it:
CREATE proc [dbo].[uspSendEmail]
#subject nvarchar(max),
#to nvarchar(max),
#body nvarchar(max),
#file nvarchar(max)='',
#cc nvarchar(max)='',
#bcc nvarchar(max)='',
#query nvarchar(max)='',
#attach_query_result_as_file tinyint=0,
#query_attachment_filename nvarchar(max)=''
as
EXEC msdb.dbo.sp_send_dbmail #profile_name = NULL,
#body_format = 'HTML',
#copy_recipients = #cc,
#blind_copy_recipients = #bcc,
#recipients = #to,
#body = #body,
#subject = #subject,
#file_attachments = #file,
#query = #query,
#attach_query_result_as_file = #attach_query_result_as_file,
#query_attachment_filename = #query_attachment_filename
return 0

Sending Multiple DB mail using curser

I have a Table which have ID, Subject, Result and Email columns. I want to send Subject and result to user.
I try to use bellow code to do this
Declare #ID INT
Declare Agent Cursor for
SELECT ID FROM myTable
GROUP BY ID
OPEN Agent
FETCH NEXT FROM Agent INTO #ID
While (##Fetch_Status = 0)
Begin
DECLARE #email NVARCHAR(MAX)
SET #email = (SELECT email FROM myTable
WHERE ID = #ID)
DECLARE #query NVARCHAR(MAX)
SET #query = 'SELECT ID, Subject, Result FROM myTable WHERE ID = ''#ID'''
--print EXEC #query
EXEC msdb.dbo.sp_send_dbmail
#profile_name='Reports',
#recipients='my#email.com',
#subject = 'Results',
#body = ' ',
#body_format = 'HTML',
#query = #query,
#query_result_header = 0,
#exclude_query_output = 1,
#append_query_error = 1,
#attach_query_result_as_file = 1,
#query_attachment_filename = 'qry.txt',
#query_result_no_padding = 1
FETCH NEXT FROM Agent INTO #ID
end
CLOSE Agent
DEALLOCATE Agent
However when I execute this i do not get any error. Only get a message telling
Command(s) completed successfully
I couldn't get the
Mail (Id: 16) queued.
message which should normally come with this kind of executions.
Where is the bug in this script?
You're treating #ID as both numeric and a string (I have no idea what it actually IS in "myTable") - and there's a possibility of a data conversion error on the first execution of sp_send_dbmail.
SET #email = (SELECT email FROM myTable
WHERE ID = #ID)
SET #query = 'SELECT ID, Subject, Result FROM myTable WHERE ID = ''#ID'''
Try changing the above to treat #ID as a numeric.
SET #query = 'SELECT ID, Subject, Result FROM myTable WHERE ID = #ID'
Presumably, you want the query to contain the id. I think you want:
SET #query = REPLACE('SELECT ID, Subject, Result FROM myTable WHERE ID = #ID', '#ID', #ID);
As explained in the documentation:
Note that the query is executed in a separate session, so local variables in the script calling sp_send_dbmail are not available to the query.

SQL Server 2005 Database Mail Failures

I am running database mail on a SQL 2005 box. Occasionally mails fail to send, by quering the msdb.dbo.sysmail_mailitems table i can see there are items with a sent_status of "2", which is failed. I can query the sysmail_faileditems table to list all failed mails.
Is there anyway i can process/re-send these failed mail's?
Would it be reasonable to create a daily job to query this table looping through using a CURSOR to re-send the mails one by one, and then delete them from the table one by one.
If you have a better suggestion / ideas then please let me know.
Many thanks Karl
First up, i suggest you query faileditems to determine your main cause of failure:
SELECT items.subject ,
items.last_mod_date ,
l.description
FROM dbo.sysmail_faileditems AS items
INNER JOIN dbo.sysmail_event_log AS l ON items.mailitem_id = l.mailitem_id
If it's nothing that can be easily fixed, you can re-send them by looping through the sysmail_mailitems table and re-sending them based on the failure type (timeouts etc) in the faileditems log - some good examples in the suggestions of this blog: http://justgeeks.blogspot.co.uk/2007/05/resending-sysmail-emails.html
My personal favourite:
CREATE PROCEDURE sysmail_resend_timeout
AS
BEGIN
SET NOCOUNT ON
DECLARE SYSMAIL_LOG_RESEND_CURSOR CURSOR READ_ONLY
FOR
SELECT DISTINCT
l.mailitem_id ,
p.name ,
m.recipients ,
m.subject ,
m.body_format ,
m.body
FROM msdb.dbo.sysmail_log l WITH ( NOLOCK )
JOIN msdb.dbo.sysmail_mailitems m WITH ( NOLOCK ) ON m.mailitem_id = l.mailitem_id
JOIN msdb.dbo.sysmail_profile p WITH ( NOLOCK ) ON p.profile_id = m.profile_id
WHERE l.event_type = 3
AND m.sent_status = 2
AND l.description LIKE '%The operation has timed out%'
ORDER BY l.mailitem_id
OPEN SYSMAIL_LOG_RESEND_CURSOR
WHILE ( 1 = 1 )
BEGIN
DECLARE #mailitem_id INT ,
#profile_name NVARCHAR(128) ,
#recipients VARCHAR(MAX) ,
#subject NVARCHAR(255) ,
#body_format VARCHAR(20) ,
#body NVARCHAR(MAX)
FETCH NEXT FROM SYSMAIL_LOG_RESEND_CURSOR INTO #mailitem_id, #profile_name, #recipients, #subject, #body_format, #body
IF NOT ##FETCH_STATUS = 0
BEGIN
BREAK
END
PRINT CONVERT(VARCHAR, GETDATE(), 121) + CHAR(9) + CONVERT(VARCHAR, #mailitem_id) + CHAR(9) + #recipients
EXEC msdb.dbo.sp_send_dbmail
#profile_name = #profile_name ,
#recipients = #recipients ,
#subject = #subject ,
#body_format = #body_format ,
#body = #body
UPDATE msdb.dbo.sysmail_mailitems
SET sent_status = 3
WHERE mailitem_id = #mailitem_id
END
CLOSE SYSMAIL_LOG_RESEND_CURSOR
DEALLOCATE SYSMAIL_LOG_RESEND_CURSOR
END
GO
I know it's not really the answer you want to hear, but I always try and decouple the mail feature. I might use a trigger to spawn an external process if the mail sending needs to be timely, but I let the external script do the actual job of sending the mail. That way transient connection errors are taken care of by the MTA, and I don't have to worry about special book-keeping algorithms.