Link to home
Start Free TrialLog in
Avatar of BillTr
BillTr

asked on

Lotus Notes Error handling when emailing - can I catch invalid names?

I have a script that sends mail based on a list of names in a document. The function that does the mail also creates new documents based on transactions entered by the user. Kind of a child document...

 The error I received "Unable to send Mail, No match found in address book" is valid because there was one person in the list who is no longer listed in the address book. But when it presented the error to the User, it also apparrently dropped out of the function it was in, without processing the other names and without processing the child doc.

I'm wondering if I can add some error handling so that the code can handle invalid names in the list. If I can trap for the error, I can have the code mail the Admin of the database about the missing name and (more importantly) let the user continue their work.

Function MailPurchAlert(Amount As Variant, note As notesdocument, Action As String)
	Dim workspace As New NotesUIWorkspace
	Dim session As New notessession
	Dim db As NotesDatabase
	Set db = session.CurrentDatabase 
	Dim item As NotesItem
	GROUP = note.MAILTO
	Set item = note.GetFirstItem("MAILTO")
	FUND = note.Fund(0)
	SETTLEDATE = Cstr(note.Date(0))
	Purchase = Cstr(note.Purchase(0))
	Redemption= Cstr(note.Redemption(0))
	Cancel = Cstr(note.Cancel_1(0))
	PurchaseOld = Cstr(note.Purchase_1(0))
	RedemptionOld= Cstr(note.Redemption_1(0))
	CancelOld = Cstr(note.Cancel_1(0))
	'Msgbox "purchalert"
	If Isempty(GROUP) Then
			' do nothing
	Else
			'Forall a In note.MAILTO
	'	Set item = note.getfirstitem("MAILTO")
		
		Set SendTo = New NotesName(a)
		Set NotifyDoc = New NotesDocument(Db)
		NotifyDoc.Form = "Memo"
		NotifyDoc.SendTo = SendTo.Abbreviated
		NotifyDoc.Subject = "A CHANGE to a Large $ Trade Notification"
		Set Body = New NotesRichTextItem(NotifyDoc, "Body")
		Msg =  "Please note that the " & FUND & " fund will have a large dollar " & Action & " trade in the amount of "
		Body.AppendText(Msg)
		Body.AddNewLine(1)
		Msg = Amount & " on Settlement Date of " & SETTLEDATE & ". This represents a change in what was reported to you."
		Body.AppendText (Msg)
		Body.AddNewLine(2)
		Body.AppendText "To view more information regarding this large dollar trade notification, please review the "
		Body.AddNewLine(1)
		Body.AppendText "Summary page of the Large $ Trade Notification database. "
		Body.AddNewLine(2)
		Body.AppendText "Click here to go to the document: "
		Body.AppendDocLink Note, "Large Dollar"
		Body.AddNewLine(2)
		Body.AppendText "For questions regarding this trade notice, please contact the individual that submitted this notice via the Large Dollar Trade Notification database."	
		Body.AddNewLine(2)
		Body.AppendText " For questions about the database, please contact your Admin."	
	End If
	For x=0 To Ubound(item.values)
		Call notifyDoc.Send(False,item.values(x))
		Print "Mail Send to: "&Cstr(item.values(x))
	Next
				'NotifyDoc.Send False
			'End Forall
	
End Function

Open in new window

ASKER CERTIFIED SOLUTION
Avatar of Bill-Hanson
Bill-Hanson
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of BillTr
BillTr

ASKER

This is very helpful!
Can I leverage this to skp the name? Say I had the GOTO jump to right before the next statement...to force it to keep processing from there..?

The problem is that I drop out of the code when I hit the bad name, when what I want it to do is skip that name and go on to the next. (I've been experimenting with some code to check the name against the address books first, but it does slow things down. I'm still trying to get that to work.)
The code I posted already skips the name and continues to send emails.

Here's what happens...

(1) In the For loop, the On Error statement on line 48 sets a trap to catch for all errors.
(2) If notifyDo.Send on line 49 raises an error, the code will automatically jump to the CATCH_SEND_ERROR label on line 57.
(3) The example displays the error in a message box on line 58, but you should actually handle the error instead of just displaying it.
(4) When the code reaches line 59, the Resume Next statement causes the code to jump to the line immediately following the one that caused the error (line 50).
(5) The On Error statement on line 50 removes the trap (setting the Goto to zero removes a trap).
(6) The For loop continues to execute, sending emails until the end of the array (item.values) is reached.
(7) The "Exit Function" on line 55 exits he function normally and prevents the code from accidentally entering the CATCH_SEND_ERROR block.

This was just an example of setting an error trap for a single line of code regardless of the actual error raised.  We can improve this by finding out what the error code is for an "Unable to send mail" error.  In LotusScript, there are a few functions that tell you information about the error:

Err - Contains the error code.
Error$ - Contains the error message.
Erl - Contains the line number where the error occurred.

You can also define and throw your own errors with the "Error" statement.  All you do is pass an error code and message.  Lotus uses error codes in these ranges 0-323, 4000-4697 , and 13000-13019.  They are defined in the files "lserr.lss", "lsxbeerr.lss", and "lsuierr.lss".  I use the range 2000-3999 for my custom errors.  Here's an example.  Say you want to provide a better error message that the default "Object varible not set".  Here's how you could do it with the Error statement:

set doc = view.GetDocumentByKey("SOMEKEY", True)
if (doc is nothing) Error 2000, "Unable to locate the SOMEKEY document!"
doc.SomeField = ""

I like this better then using Msgbox and Exit Function because it fits on a single line.   :)

Anyway, back to our send email problem.  We want to improve the error handling in our first script, so we need to know the error code for "Unable to send mail".  If you look in lsxbeerr, you'll see that lsERR_NOTES_NO_MATCH =  4294, but that is kind of hard to determine just from reading the file, so here's a simple agent that will tell us the error code.  We'll use this error code (4294) in our next example.
Sub Initialize
	
	On Error Goto CATCH
	Dim sess As New NotesSession()
	Dim db As NotesDatabase
	Dim doc As NotesDocument
	Set db = sess.CurrentDatabase
	Set doc = db.CreateDocument()
	doc.Form = "Memo"
	doc.Subject = "Checking Error Code"
	Call doc.Send(False, "CN=Bad Name/O=Bad Org")
	Exit Sub
CATCH:
	Msgbox "Error " & Cstr(Err) & " on line " & Cstr(Erl) & Chr(10) & Error$
	End
	
End Sub

Open in new window

Avatar of BillTr

ASKER

Perfect! Thanks!
Avatar of BillTr

ASKER

Exactly what I was looking for!
No problem.  Here's another example that keeps an array of failures, then sends a single email to an administrator containing all of the names.  Also, I've added a specific error trap so that we avoid setting and clearing the single line trap.

Function MailPurchAlert(Amount As Variant, note As notesdocument, Action As String)
      
      On Error 4294 Goto CATCH_SEND_ERROR ' Set up a trap to catch only lsERR_NOTES_NO_MATCH errors.
      Dim workspace As New NotesUIWorkspace
      Dim session As New notessession
      Dim db As NotesDatabase
      Set db = session.CurrentDatabase
      Dim item As NotesItem
      GROUP = note.MAILTO
      Set item = note.GetFirstItem("MAILTO")
      FUND = note.Fund(0)
      SETTLEDATE = Cstr(note.Date(0))
      Purchase = Cstr(note.Purchase(0))
      Redemption= Cstr(note.Redemption(0))
      Cancel = Cstr(note.Cancel_1(0))
      PurchaseOld = Cstr(note.Purchase_1(0))
      RedemptionOld= Cstr(note.Redemption_1(0))
      CancelOld = Cstr(note.Cancel_1(0))
      
      If (Not Isempty(GROUP)) Then
            Set SendTo = New NotesName(a)
            Set NotifyDoc = New NotesDocument(Db)
            NotifyDoc.Form = "Memo"
            NotifyDoc.SendTo = SendTo.Abbreviated
            NotifyDoc.Subject = "A CHANGE to a Large $ Trade Notification"
            Set Body = New NotesRichTextItem(NotifyDoc, "Body")
            Msg = "Please note that the " & FUND & " fund will have a large dollar " & Action & " trade in the amount of "
            Body.AppendText(Msg)
            Body.AddNewLine(1)
            Msg = Amount & " on Settlement Date of " & SETTLEDATE & ". This represents a change in what was reported to you."
            Body.AppendText (Msg)
            Body.AddNewLine(2)
            Body.AppendText "To view more information regarding this large dollar trade notification, please review the "
            Body.AddNewLine(1)
            Body.AppendText "Summary page of the Large $ Trade Notification database. "
            Body.AddNewLine(2)
            Body.AppendText "Click here to go to the document: "
            Body.AppendDocLink Note, "Large Dollar"
            Body.AddNewLine(2)
            Body.AppendText "For questions regarding this trade notice, please contact the individual that submitted this notice via the Large Dollar Trade Notification database."      
            Body.AddNewLine(2)
            Body.AppendText " For questions about the database, please contact your Admin."      
      End If
      
      Dim failures As Variant ' Use an array to save the names that failed to send.
      Dim userName As String ' Save the name so that the error block can see it (the error block would know what item.values was, but not x).
      For x=0 To Ubound(item.values)
            userName = item.values(x)
            Call notifyDoc.Send(False, userName)
            Print "Mail Send to: " & Cstr(userName)
      Next
      
      ' Check the failures array.
      If (Isarray(failures)) Then
            Dim ErrorDoc As NotesDocument
            Set ErrorDoc = Db.CreateDocument()
            ErrorDoc.Form = "Memo"
            ErrorDoc.Subject = "Errors from " & Db.Title
            ErrorDoc.SendTo = "Your DB Admin"
            Dim txtBody As String
            txtBody = "Unable to send email to the following users:" & Chr(10)
            Forall v In failures
                  txtBody = txtBody & v
            End Forall
            ErrorDoc.Body = txtBody
            Call ErrorDoc.Send(False)
      End If
      Exit Function ' So we don't pass through into our error handler.
      
CATCH_SEND_ERROR:
      If (Isarray(failures)) Then Redim failures(Ubound(failures)) Else      Redim failures(0) ' Init or expand the array.
      failures(Ubound(failures)) = userName ' Set the last element of the array to the user's name.
      Resume Next ' Keep processing.  You could also "Exit Function" or "End" here to stop processing.
      
End Function
By the way, there is another way to do a single-line error trap.  You have:

            On Error Goto CATCH_SEND_ERROR ' Set a wildcard trap.
            Call notifyDoc.Send(False,item.values(x))
            On Error Goto 0 ' Remove the wildcard trap.


Instead, you should do it as follows:
            On Error Resume Next ' Set a wildcard trap.
            Call notifyDoc.Send(False,item.values(x))
            On Error Goto 0 ' Remove the wildcard trap.
            If Err <> 0 Then
                  If (Isarray(failures)) Then Redim Preserve failures(Ubound(failures)+1) Else Redim failures(0)
                  failures(Ubound(failures)) = userName ' Set the last element of the array to the user's name.
                  Err = 0 'resets the status of the error, since there is no RESUME in this format
            End If
Hey, good point.

Also, you re-dimmed your array correctly!  Redim Preserve failures(Ubound(failures)+1)

I must have been thinking in JavaScript again.  failures[failures.length] = "new element"

Oops!
Avatar of BillTr

ASKER

Can't I use On Error more than once? In another program I have the users drop and add personal views.
The views are dropped via a .remove and that will fail with an object not set if the view in question has
already been removed.

So, I inserted the on error to send the user a popup msg when the view is already gone. This works fine on the first view, but fails if it encounters the error on the next.

I'm attaching the code, but be kind, I realize I should rewrite the whole thing but I just want to understand why it's failing here. Thanks

This is the second STEP,,,the first one worked the same way. IF the first step processes on error then the second step will fail to.

  ' ***********************************************************************************************************
     ' If ret% <> 2 Then          
        '  ret% = Msgbox(" Delete your Private view called Dashboard\2. Current Work.  Continue? ", 3 + 16, "No Data Entered")
		Set view = db.GetView( "My Dashboard\2. Current Work" ) 
    '      Msgbox ret%
          'If ret% = 6 Then
     ' make sure they are deleting the personal view, not actual design element
' Then remove the view, try using error2 instead of on error
		On Error2 Goto CATCH_CURRENTWORK_ERROR ' Set a wildcard trap.
		If Not(Isempty(view.Readers)) Then
			Call view.Remove 
			
                '    Msgbox "The Current Work view has been updated. You must close and re-open the database before you will see the changes"
		End If
		On Error Goto 0 ' Remove the wildcard trap.
		Goto FIN_CURRENTWORK  ' skip error and continue on
CATCH_CURRENTWORK_ERROR:
		Messagebox "It looks like the Current Work view is up to date or has already been removed. "
		On Error Goto 0 ' Remove the wildcard trap.
		
FIN_CURRENTWORK:

Open in new window

I must say that I don't like placing error traps inline with the main code block, but there's nothing syntactically wrong with doing so.  I prefer placing all of the traps at the end of the function.  It makes things much easier to read.  Also, the result of trapping an error should end with a Resume or Exit call.  In your code above, there are several statements that don't make sense.

Line 9:  On Error2 Goto CATCH_CURRENTWORK_ERROR ' Set a wildcard trap.
Since you specified an error code, this is not a wildcard trap.  It will only trap errors with a code of 'Error2'.  I assume that is just an error in the comment.

Line 15: On Error Goto 0 ' Remove the wildcard trap.
Why? This line does remove the wildcard trap, but none was set.

Line 16: Goto FIN_CURRENTWORK  ' skip error and continue on
Error traps should not be setup like standard blocks that you can just skip or execute (more on this below).  Also, using GOTO makes the code hard to read and results in 'spaghetti' code.  The general rule of thumb is: "If you can't find a way to do without a GOTO statement, you need to move some code to a new function".  That said, we have to deal with 'some' GOTO statements due to LS's limited error handling capabilities (ie: no try..catch constructs).

Line 19: On Error Goto 0 ' Remove the wildcard trap.
Again, there is no wildcard trap set.  Also, this is not the right way to recover from an error.  You need to use a Resume or Exit statement.

The 'Resume' keyword is how you recover from an error.  Not only does it send the code pointer to a specific location, but it also clears the error status flags (Err, Error$, Erl).  It takes one of three parameters:

Resume 0
This will automatically send the code pointer back to execute the same line that caused the error.
This is handy when you can fix the problem and want to try again.

Resume Next
This will automatically send the code pointer back to execute the line directly after the one that caused the error.  This is handy when you can recover from the error in the error handler block and want to continue processing anyway.

Resume LABEL
This will send the code pointer to any label you define.  This is probably what you want to use in your code above.

Here is how I would re-write this block of code:

Sub YourSub()
 
	' 
	' Your code before the block you posted...
	' 
	Set view = db.GetView( "My Dashboard\2. Current Work" )
	On Error2 Goto CATCH_CURRENTWORK_ERROR ' Trap errors caused by view.Remove.
	If Not(Isempty(view.Readers)) Then
		Call view.Remove
	End If
	'
	' Contune processing your code...
	'
	Exit Sub
	
CATCH_CURRENTWORK_ERROR:
	Messagebox "It looks like the Current Work view is up to date or has already been removed. "
	Resume Next
	
End Sub

Open in new window

Avatar of BillTr

ASKER

The error seems tied to Notes having problems dealing with a view that does not exist. Why it works on the first occurance but fails on the rest is the issue. I experimented with checking for the existance of
the view first. There was another post here that dealt with a similar issue when trying to run an archive.

Anyway, long story-short, I inserted the attached if statement and got the code to work.
On Error Goto CATCH_ACTIONITEM_ERROR ' Set a wildcard trap.
		'If Not(Isempty(view.Readers)) Then <-- this fails
		If view Is Nothing Then   ' <-- this works
			Goto CATCH_ACTIONITEM_ERROR
		Else
			Call view.Remove 
		End If
		
               ' Msgbox "The Action Items view has been updated. You must close and re-open the database before you will see the changes"
		'End If			
		On Error Goto 0 ' Remove the wildcard trap.
		
		Goto FIN_ACTIONITEM  ' skip error and continue on
		
CATCH_ACTIONITEM_ERROR :
		Messagebox "I think the Action Items view is up to date or has already been removed. "
		On Error Goto 0 ' Remove the wildcard trap.
		
FIN_ACTIONITEM:

Open in new window

The reason that it is not trapping subsequent errors is line 18.   You're removing all error traps on the first error.
Have a look at the error handling here: https://github.com/dev-ng/ls-dl