I am using SQL Server 2008 and am trying to send emails to customers about their overdue ADV returns. There is a 30 day, 45 day and 50 day letter. I have noticed this issue currently on the 30 day and 45 day letters.
I have a query that pulls the right people. For instance on the 30 day letter today it pulled 4 records and they are correct. for instance the data looks like
john item1
david item1
david item2
fred item1
So what is happening is I get 4 emails, but john is missing and david gets 3 emails one of which is a duplicate.
Here is the code for sending the emails
OPEN C1
FETCH NEXT FROM C1
INTO #sronum, #transdate,#item,#desc, #days, #email, #NAME
WHILE ##FETCH_STATUS = 0
BEGIN
FETCH NEXT FROM C1
INTO #sronum, #transdate,#item,#desc,#days, #email, #NAME
set #email = 'internal test email address'
set #subject = 'ADV Return is 30 days past due'
set #body = 'Dear ' + #name + ', </br></br>' + more text
EXEC msdb.dbo.sp_send_dbmail
#recipients = #email
,#Body = #Body
,#subject = #Subject
,#body_format = 'html'
,#exclude_query_output = 1
END
CLOSE C1
DEALLOCATE C1
A bit commented;
OPEN C1
-- Fetch first result and throw it away since next fetch will be done
-- before using the values
FETCH NEXT FROM C1 INTO
#sronum, #transdate,#item,#desc, #days, #email, #NAME
-- If the fetch went well, loop
WHILE ##FETCH_STATUS = 0
BEGIN
-- Fetch the next row and ignore if it went well for now
FETCH NEXT FROM C1 INTO
#sronum, #transdate,#item,#desc,#days, #email, #NAME
set #email = 'internal test email address'
set #subject = 'ADV Return is 30 days past due'
set #body = 'Dear ' + #name + ', </br></br>' + more text
-- Send a mail to the possibly valid address
EXEC msdb.dbo.sp_send_dbmail
#recipients = #email
,#Body = #Body
,#subject = #Subject
,#body_format = 'html'
,#exclude_query_output = 1
-- Jump back to the start of the loop to check if the mail just sent
-- was supposed to be sent.
END
CLOSE C1
DEALLOCATE C1
The simplest fix is to move the second fetch after the email has been sent. That will check if the first result was valid, send a mail, fetch a new row and go back to the validity check to possibly send another mail.
Related
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
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
I have a table filled with oldUserID, newUserID, name and email. I want to use sp_send_dbmail to the email on each row. For example:
oldUserID | newUserID | name | email
21213125 | 2355233571 | Tom | tom#gmail.com
65465465 | 4564884664 | Mat | mat#gmail.com
And so on for 200 rows. Is there any way to send an sp_send_dbmail to the email on each row including oldUserID and newUserID? The output in the mail would be something like:
"Your old user id: 21213125, your new user id: 2355233571"
I would appreciate not to enter each emailadress manually.
Thank you!
DECLARE
#txt NVARCHAR(MAX)
, #name NVARCHAR(60)
, #email VARCHAR(100)
DECLARE cur CURSOR FAST_FORWARD READ_ONLY LOCAL FOR
SELECT 'Your old user id: ' + CAST(oldUserID AS NVARCHAR(100))
+ ', your new user id: ' + CAST(newUserID AS NVARCHAR(100)), name, email
FROM ...
OPEN cur
FETCH NEXT FROM cur INTO #txt, #name, #email
WHILE ##fetch_status = 0
BEGIN
EXEC msdb.dbo.sp_send_dbmail #profile_name = ...
, #recipients = #email
, #subject = #name
, #body = #txt
, #body_format = 'HTML'
FETCH NEXT FROM cur INTO #txt, #name, #email
END
CLOSE cur
DEALLOCATE cur
When a user uses our site to process certain requests, upon exiting the site, an email with a link goes out to that user asking him/her to click the link to take a survey and share his/her user experiences with us.
Below is the stored procedure that I have written that does as described above.
ALTER proc [dbo].[SendSurvey]
AS
BEGIN
Declare #sender nvarchar(200)
declare #dept nvarchar(200) = ''
declare #loc nvarchar(200) = ''
declare #dteCreated nvarchar
declare #RequestID nvarchar(50) = ''
declare #authorizedname nvarchar(200) = ''
declare #email nvarchar(200) = ''
declare #message nvarchar(1000) = ''
declare #mailid int = 0
SET QUOTED_IDENTIFIER OFF;
SELECT
#email = email, #mailid=ID, #message = #message,
#RequestID = RequestID,
#authorizedname = SUBSTRING(submittedBy, CHARINDEX(',', submittedBy) + 1, LEN(submittedBy) - CHARINDEX(',', submittedBy) + 1)
+ ' ' + SUBSTRING(submittedBy, 1, CHARINDEX(',', submittedBy) - 1),
#loc = Bldg, #dtecreated = DateCreated, #dept = Department
FROM
Survey
WHERE
email = #email
AND Email IS NOT NULL OR Email != ''
AND (orderStatus != 1)
SELECT #message = 'This is a computer generated email message.
Please DO NOT use the REPLY button above to respond to this email.
Dear '+ #authorizedname +':
Thank you for using the order processing system.
Please click the link below to complete a survey
http://feedbacksurvey.php?rID=' +#RequestID+'&loc='+Replace(#loc,' ', '%20')+'&dept='+Replace(#dept,' ', '%20')+'
Regards,
web admin.'
EXEC msdb.dbo.sp_send_dbmail
#profile_name = 'Customer Feedback Survey',
#recipients = #Email, -- your email
#subject = 'Customer Feedback Survey',
#body = #message;
UPDATE Survey
SET orderStatus = 1
WHERE orderStatus != 1 AND ID = #mailid
END
There are two problems with the stored procedure.
There is a column orderStatus which is a BIT data type with True (1) of false(0) value.
If the orderstatus is false, then send emails with records associated with it.
After sending the email, update orderstatus to true so the email doesn't get sent a second time.
This is not working. When I execute the stored procedure where all records on the table have orderstatus set to True, email still goes out.
the second problem that I am having is that the code is not sending out all records where orderStatus is True. It just sends email one at a time.
We would like emails to be send out for ALL records where orderstatus = 1 (True).
Any ideas what I am doing wrong?
You mixed AND with OR in your WHERE clause. The results will include all rows where Email != '', regardless of the other conditions.
Use parens to make this work:
WHERE email=#email
AND (Email IS NOT NULL or Email != '')
AND (orderStatus != 1)
As for why it's sending one email at a time, you are using your query to populate scalar variables.
SELECT #email = email...
Will result in #email being populated with one value, no matter how many rows the query returns.
the second problem that I am having is that the code is not sending out all records where orderStatus is True. It just sends email one at a time.
Yeah - that's how sp_send_dbmail works. You'll need a cursor to send > 1 email.
DECLARE c CURSOR LOCAL FAST_FORWARD READ_ONLY FOR
SELECT ....
FETCH NEXT FROM c INTO ....
WHILE ##FETCHSTATUS = 0 BEGIN
EXEC sp_send_dbmail ...
FETCH NEXT FROM c INTO ....
END
CLOSE c
DELLOCATE c
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.