Email class: Difference between revisions

From m204wiki
Jump to navigation Jump to search
mNo edit summary
m (minor cleanup)
 
(9 intermediate revisions by 2 users not shown)
Line 1: Line 1:
The <var>Email</var> class is a high level,
The <var>Email</var> class is a high level,
object oriented interface to client sockets that lets you write a <var class="product">User Language</var> SMTP
object oriented interface to client sockets that lets you write a <var class="product">SOUL</var> SMTP
(Simple Mail Transfer Protocol)
(Simple Mail Transfer Protocol) request without knowledge of socket level programming
request without knowledge of socket level programming
or the format of SMTP requests and responses.
or the format of SMTP requests and responses.
   
   
The <var>Email</var> class methods let a <var class="product">User Language</var> program act as an SMTP client.
The <var>Email</var> class methods let a <var class="product">SOUL</var> program act as an SMTP client.
SMTP clients send mail requests
SMTP clients send mail requests
to an SMTP server, not directly to other SMTP clients.
to an SMTP server, not directly to other SMTP clients.
The <var>Email</var> class methods only support communication with SMTP servers.
The <var>Email</var> class methods only support communication with SMTP servers.
   
   
An SMTP server will &ldquo;store and forward&rdquo; mail it receives.
An SMTP server will "store and forward" mail it receives.
This means that all
This means that all
messages are first received in entirety on the SMTP server,
messages are first received in entirety on the SMTP server,
Line 16: Line 15:
Mail delivery can be delayed by a server that is down or too busy to
Mail delivery can be delayed by a server that is down or too busy to
accept connections.
accept connections.
&ldquo;Store and forward&rdquo; gives the SMTP server the ability
"Store and forward" gives the SMTP server the ability
to retry delivery without making the SMTP client wait for completion.
to retry delivery without making the SMTP client wait for completion.
   
   
Line 22: Line 21:
This, of course, makes it impractical for a client to wait for delivery
This, of course, makes it impractical for a client to wait for delivery
confirmation, so the SMTP protocol does not provide such information.
confirmation, so the SMTP protocol does not provide such information.
A &ldquo;success&rdquo; response from an SMTP server indicates
A "success" response from an SMTP server indicates
only that the server has accepted the request and will
only that the server has accepted the request and will
attempt to deliver the mail to the recipients.
attempt to deliver the mail to the recipients.
Line 44: Line 43:
<p class="note">'''Note:'''
<p class="note">'''Note:'''
To use <var>Email</var> objects under versions of Model 204 earlier than 7.5, you must have licensed <var class="product">[[Janus TCP/IP Base]]</var>, <var class="product">[[Janus Sockets]]</var>, '''and''' <var class="product">[[Janus SOAP]]</var>. Under version 7.5 and later,
To use <var>Email</var> objects under versions of Model 204 earlier than 7.5, you must have licensed <var class="product">[[Janus TCP/IP Base]]</var>, <var class="product">[[Janus Sockets]]</var>, '''and''' <var class="product">[[Janus SOAP]]</var>. Under version 7.5 and later,
you do not require <var class="product">[[Janus SOAP]]</var>. </p>
you do not require <var class="product">Janus SOAP</var>. </p>


For information about using SOUL objects, see [[Janus SOAP User Language Interface]] and
For information about using SOUL objects, see [[Object oriented programming in SOUL]] and
[[Getting started with object-oriented programming for User Language programmers]].
[[Getting started with OOP for User Language programmers]].


==Email class summary==
==Email class summary==
Line 67: Line 66:
   
   
[[List of Email methods]] contains a list of all the methods in the <var>Email</var> class.
[[List of Email methods]] contains a list of all the methods in the <var>Email</var> class.
==Dummy SMTP Server example==
This is an example of a very basic SMTP Server: it simply accepts <var>Email</var> objects from an SMTP client, and in turn, sends a response.
It does not store or retrieve messages.
No <var class="product">Janus</var> port definitions are shown.
For comments about defining <var class="product">Janus</var> ports for the <var>Email</var> class, see the [[#jport|earlier]] remarks.
===Dummy SMTP Server===
<b>emclass.ul</b>:
<p class="code"><nowiki>***********************************************************************
*                                                                    *
* encoding enumerations.                                              *
*                                                                    *
***********************************************************************
enumeration encoding
  public
  value base64
  value none
  end  public
end enumeration
***********************************************************************
*                                                                    *
* emailPart class.                                                    *
*                                                                    *
***********************************************************************
class emailPart
public
  variable headers is object stringlist
  variable body is object stringlist
  function type is string len 255
  function encoding is string len 255
  function name is string len 255
  function contentlist is object stringlist
  function content is longstring
end public
***********************************************************************
*                                                                    *
* Return complete contentlist from an emailPart                      *
*                                                                    *
***********************************************************************
function contentlist is object stringlist
return %this:body
end function
***********************************************************************
*                                                                    *
* Decode and return content from an emailPart                        *
*                                                                    *
***********************************************************************
function content is longstring
%toke is object stringTokenizer
%token is string len 128
%encoding is string len 128
%lf  is string len 1
%i    is float
%j    is float
%content is longstring
%concat  is boolean initial(True)
%temp    is longstring
if %this:body eq null then return ''; end if
%lf = '0A':x
for %i from %i + 1 to %this:body:count
  if %this:encoding eq 'base64' then
      %content = %content with %this:body(%i)
  elseif %this:encoding eq 'quoted-printable' then
      %temp = %this:body(%i):Unspace(Leading=False, Trailing=True, -
        Compress=False)
      if %concat = False then
        %content = %content %lf
      end if
      for %j from 1 to %temp:Length - 2
        if %temp:Substring(%j, 1) = '=' then
            %content = %content %temp:Substring(%j + 1, 2): -
            HexToString:AsciiToEbcdic(characterEncode=true)
            %j = %j + 2
        else
            %content = %content %temp:Substring(%j, 1)
        end if
      end for
      if %j <= %temp:Length then
        for %j from %j to %temp:Length
            %content = %content %temp:Substring(%j, 1)
        end for
      end if
      if %content:Right(1) = '=' then
        %concat = True
        %content = %content:TrimRight(1)
      else
        %concat = False
      end if
  else
      %content = %content %this:body(%i) with %lf
  end if
end for
if %this:encoding eq 'base64' then
  %content = %content:base64ToString
  if %this:type:left(5) = 'text/' then
      %content = %content:AsciiToEbcdic(CharacterEncode=true)
  end if
end if
return %content
end function
***********************************************************************
*                                                                    *
* Return the name (if present) of an emailPart                        *
*                                                                    *
***********************************************************************
function name is string len 255
%type is string len 255
%name is string len 255
%temp is string len 255
%i    is float
%n    is float
for %i from 1 to %this:headers:count
  %temp = %this:headers(%i)
  if 'Content-Disposition:':toUpper eq %temp:word(1):toUpper then
      for %i from %i + 1 to %this:headers:count
        if %temp:word(1):right(1) eq ':' then loop end; end if
        %temp = %temp %this:headers(%i)
      end for
      %i = %i - 1
      %n = %temp:positionOf(' filename=')
      if %n gt 0 then
        %name = %temp:subString(%n)
      end if
      loop end;
  end if
  if 'Content-Type:':toUpper eq %temp:word(1):toUpper then
      for %i from %i + 1 to %this:headers:count
        if %temp:word(1):right(1) eq ':' then loop end; end if
        %temp = %temp %this:headers(%i)
      end for
      %i = %i - 1
      %n = %temp:positionOf(' name=')
      if %n gt 0 then
        %name = %temp:subString(%n)
      end if
  end if
end for
%name = %name:word(2, spaces=' =')
if %name:left(1) = '"' then %name = %name:trimLeft(1); end if
if %name:right(1) = '"' then %name = %name:trimRight(1); end if
return %name
end function
***********************************************************************
*                                                                    *
* Return the encoding (if present) of an emailPart                    *
*                                                                    *
***********************************************************************
function encoding is string len 255
%enc  is string len 255
%temp is string len 255
%i    is float
%n    is float
for %i from 1 to %this:headers:count
  %temp = %this:headers(%i)
  if 'Content-Transfer-Encoding:':toUpper eq %temp:word(1):toUpper then
      for %i from %i + 1 to %this:headers:count
        if %this:headers(%i):word(1):right(1) eq ':' then loop end; end if
        %temp = %temp %this:headers(%i)
      end for
      loop end;
  end if
end for
if 'Content-Transfer-Encoding:':toUpper eq %temp:word(1):toUpper then
  %enc = %temp:word(2)
  if %enc:left(1) = '"' then %enc = %enc:trimLeft(1); end if
  if %enc:right(1) = '"' then %enc = %enc:trimRight(1); end if
end if
if %enc = '' then %enc = '7bit'; end if
return %enc
end function
***********************************************************************
*                                                                    *
* Return the content type of an emailPart                            *
*                                                                    *
***********************************************************************
function type is string len 255
%type is string len 255
%temp is string len 255
%i    is float
%n    is float
for %i from 1 to %this:headers:count
  %temp = %this:headers(%i)
  if 'Content-Type:':toUpper eq %temp:word(1):toUpper then
      for %i from %i + 1 to %this:headers:count
        if %temp:word(1):right(1) eq ':' then loop end; end if
        %temp = %temp %this:headers(%i)
      end for
      %i = %i - 1
      loop end;
  end if
end for
%type = %temp:word(2)
if %type:left(1) = '"' then %type = %type:trimLeft(1); end if
if %type:right(1) = '"' then %type = %type:trimRight(1); end if
if %type = '' then %type = 'text/plain'; end if
return %type
end function
end class
***********************************************************************
*                                                                    *
* receivedEmail class.                                                *
*                                                                    *
***********************************************************************
class receivedEmail
public
  variable headers is object stringlist
  variable body is object stringlist
  variable rcptList is object stringlist
  variable fromHost is string len 128
  variable from    is string len 128
  function header -
    (%name is string len 255, %occur is float default(1)) -
    is string len 255
  function contentList (%x is float) is object stringlist
  function part (%number is float) is object emailPart
  function parts is float
end public
***********************************************************************
*                                                                    *
* Return a header from the email message                              *
*                                                                    *
***********************************************************************
function header -
  (%name is string len 255, %occur is float default(1)) -
  is string len 255
%i is float
%toke is object stringTokenizer
%token is string len 255
for %i from 1 to %this:headers:count
  %toke  = %this:headers(%i):stringTokenizer
  %token = %toke:nextToken:toUpper
  %toke:discard
  if %name:toUpper eq %token then loop end; end if
end for
if %i > %this:headers:count then return ''; end if
%token = %this:headers(%i)
if %this:headers(%i):right(1) ne ';' then return %token; end if
for %i from %i + 1 to %this:headers:count
  %token = %token %this:headers(%i)
  if %this:headers(%i):right(1) ne ';' then loop end; end if
end for
return %token
end function
***********************************************************************
*                                                                    *
* Return a "part" of an email message as an emailPart object.        *
*                                                                    *
***********************************************************************
function part (%number is float) is object emailPart
%i is float
%n is float
%bound is string len 72
%part  is object emailPart
%encoding is string len 255
%partNumber is float
%cont is boolean initial(false)
%mime is boolean initial(false)
%temp is string len 255
%part = new
%part:headers = new
* Scan global headers for boundary
for %i from 1 to %this:headers:count
  %temp = %this:headers(%i)
  if 'Content-Type:':toUpper eq %temp:word(1):toUpper then
      for %i from %i + 1 to %this:headers:count
        if %this:headers(%i):word(1):right(1) eq ':' then loop end; end if
        %temp = %temp %this:headers(%i)
      end for
      %n = %temp:positionOf('boundary=')
      if %n gt 0 then
        %bound = %temp:subString(%n)
      end if
      loop end;
  end if
end for
%part:headers = new
%part:body = new
if %bound ne '' then
  %bound = %bound:substring(10)
  if %bound:left(1) = '"' then %bound = %bound:trimLeft(1); end if
  if %bound:right(1) = '"' then %bound = %bound:trimRight(1); end if
  %bound = '--' %bound;
  for %i from 1 to %this:body:count
      if %bound = %this:body(%i) then
        %partNumber = %partNumber + 1
        if %partNumber = %number then loop end; end if
      end if
  end for
  for %i from %i + 1 to %this:body:count
      if %this:body(%i) eq '' then loop end; end if
      %part:headers:add(%this:body(%i))
  end for
  for %i from %i + 1 to %this:body:count
      if %bound = %this:body(%i):substring(1, %bound:length) then
        loop end;
      end if
      %part:body:add(%this:body(%i))
  end for
else
  assert %number eq 1
  for %i from 1 to %this:headers:count
      %temp = %this:headers(%i)
      if 'Content-':toUpper eq %temp:word(1):left(8):toUpper then
        %part:headers:add(%temp)
        for %i from %i + 1 to %this:headers:count
            if %this:headers(%i):word(1):right(1) eq ':' then loop end; end if
            %part:headers:add(%this:headers(%i))
        end for
        %i = %i - 1
      end if
  end for
  for %i from 1 to %this:body:count
    %part:body:add(%this:body(%i))
  end for
end if
return %part
end function
***********************************************************************
*                                                                    *
* Return number of "parts" of an email message.                      *
*                                                                    *
***********************************************************************
function parts is float
%i is float
%n is float
%bound is string len 128
%parts is float
%temp  is longstring
* Scan global headers for boundary
for %i from 1 to %this:headers:count
  %temp = %this:headers(%i)
  if 'Content-Type:':toUpper eq %temp:word(1):toUpper then
      for %i from %i + 1 to %this:headers:count
        if %this:headers(%i):word(1):right(1) eq ':' then loop end; end if
        %temp = %temp %this:headers(%i)
      end for
      %n = %temp:positionOf('boundary=')
      if %n gt 0 then
        %bound = %temp:subString(%n)
      end if
      loop end;
  end if
end for
if %bound ne '' then
  %bound = %bound:substring(10)
  if %bound:left(1) = '"' then %bound = %bound:trimLeft(1); end if
  if %bound:right(1) = '"' then %bound = %bound:trimRight(1); end if
  %bound = '--' %bound;
else
  return 1
end if
   
   
%parts = 0
for %i from 1 to %this:body:count
  if %bound = %this:body(%i) then
      %parts = %parts + 1
  end if
end for
return %parts
end function
end class
***********************************************************************
*                                                                    *
* smtpDriver class.                                                  *
*                                                                    *
***********************************************************************
class smtpDriver
    public shared
      subroutine -
      run(%processEmail is subroutine (receivedEmail):whatever)
    end public shared
  subroutine run(%processEmail is subroutine (receivedEmail):whatever)
      %receivedEmail is object receivedEmail
      *****************************************************************
      * SMTP server program.                                          *
      *****************************************************************
      *****************************************************************
      * Static values used by the server.                            *
      *****************************************************************
      %serverId        is string len 80 initial -
            ("host.sirius-software.com running Model 204 SMTP server")
      *****************************************************************
      * End of static values used by the server.                      *
      *****************************************************************
      local subroutine (stringList):audit
      %i  is float
      for %i from 1 to %this:count
        auditText {%i}: {%this(%i)}
      end for
      end subroutine
      %remail                is object receivedEmail
      %msg                    is string len 128
      %client                is string len 128
      %command                is string len 16
      %email                  is object receivedEmail
      %esmtp                  is boolean initial(false)
      %from                  is string len 128
      %by                    is string len 128
      %received              is string len 255
      %dateTime              is string len 128
      %inputLength            is float
      %inputLine              is longstring
      %sock                  is object socket
      %toke                  is object stringTokenizer
      %token                  is string len 128
      %sock = serverSocket
      %sock:set('CHAR', 'CHAR')
      %sock:set('LINEND', '0D0A')
      %sock:set('PRSTOK', '0D0A')
      %dateTime = -
        %(system):currentTimeString('DD Mon YYYY HH:MI:SS')
      %sock:sendWithLineEnd( -
        "220 " with %serverId with " on " with %dateTime)
      %inputLength = %sock:receiveAndParse(%inputLine)
      %toke = %inputLine:stringTokenizer
      %command = %toke:nextToken:toUpper
      if %command eq 'EHLO' then
        %esmtp = true
      elseIf %command ne 'HELO' then
        auditText *** Incorrect hello message received, -
            command={%command}, closing connection
        %sock:close
        stop
      end if
      %client = %toke:nextToken
      %msg = "250 Hello " with %client with -
        " I'm delighted to meet you"
      %sock:sendWithLineEnd(%msg)
      auditText **** Entering main SMTP loop for {%client}
      repeat forever
        %inputLength = %sock:receiveAndParse(%inputLine)
        %toke = %inputLine:stringTokenizer
        %command = %toke:nextToken:toUpper
        if %command eq 'QUIT' then jump to quit; end if
        if %command ne 'MAIL' then
            auditText **** Unexpected command {%command} received, -
              terminating connection
            jump to close
        end if
        %token = %toke:nextToken
        if %token:left(5):toUpper ne 'FROM:' then
            auditText **** MAIL followed by {%token} not -
              FROM:<email-address>, terminating connection
            jump to close
        end if
        %from = %token:substring(6)
        %sock:sendWithLineEnd("250 Ok")
        %email = new
        %email:rcptList = new
        %toke = %sock:info('REMOTE'):stringTokenizer
        %email:fromHost = %toke:nextToken
        repeat forever
            %inputLength = %sock:receiveAndParse(%inputLine)
            %toke = %inputLine:stringTokenizer
            %command = %toke:nextToken:toUpper
            if %command eq 'QUIT' then jump to quit; end if
            if %command ne 'RCPT' then loop end; end if
            %token = %toke:nextToken
            if %token:left(3):toUpper ne 'TO:' then
              auditText **** RCPT followed by {%token} not -
                  TO:<email-address>, terminating connection
              jump to close
            end if
            %email:rcptList:add(%token:substring(4))
            %sock:sendWithLineEnd("250 Ok")
        end repeat
        if %email:rcptList:count eq 0 then
            auditText **** No RCPT TO:s received after a -
              MAIL FROM:, terminating connection
            jump to close
        end if
        if %command ne 'DATA' then
            auditText **** No DATA received after a RCPT TO:, -
              terminating connection
            jump to close
        end if
        %sock:sendWithLineEnd("354 End data with a single '.'")
        %email:headers = new
        setText %received = Received: from {%email:fromHost} by -
            {%serverid} ; -
            {%(system):currentTimeString('Wkd, DD Mon YYYY HH:MI:SS')}
        %email:headers:add(%received)
        repeat forever
            %inputLength = %sock:receiveAndParse(%inputLine)
            if %inputLine = '' then loop end; end if
            if %inputLine eq '.' then loop end; end if
            if %inputLine:left(2) eq '..' then
              %inputLine = %inputLine:substring(2);
            end if
            %email:headers:add(%inputLine)
        end repeat
        %email:body = new
        repeat forever
            %inputLength = %sock:receiveAndParse(%inputLine)
            if %inputLine eq '.' then loop end; end if
            if %inputLine:left(2) eq '..' then
              %inputLine = %inputLine:substring(2);
            end if
            %email:body:add(%inputLine)
        end repeat
        %sock:sendWithLineEnd("250 E-mail received")
      end repeat
      quit:
      auditText **** QUIT received
      %sock:sendWithLineEnd("221 Bye")
      close:
      auditText **** Terminating SMTP connection
      %sock:close
      %email:%processEmail
    end subroutine
end class
</nowiki></p>
<b>driver.ul</b>:
<p class="code">UTABLE LNTBL 128
b
i emclass.ul
local subroutine (receivedEmail):process
%i    is float
%j    is float
%x    is float
%ls    is longstring
%op    is string len 8
%temp  is string len 255
%ename is string len 255
%part  is object emailPart
%tp    is float
%tp = 0
setText %ename = CSURF_EMAIL_{$sir_date('YYMMDDHHMISS')}
%x = $bldproc(%tp, %ename, 'OPEN')
assert %x eq 0
&#42;
&#42; Get attachments and copy content to output procedure
&#42;
%op = 'APPEND'
for %i from 1 to %this:parts
  %part = %this:part(%i)
  for %j from 1 to %part:headers:count
      %temp = %part:headers(%j):left(255)
      %x = $bldproc(%tp, %temp, %op)
      assert %x eq 0
  end for
  for %j from 1 to %part:body:count
      %temp = %part:body(%j):left(255)
      %x = $bldproc(%tp, %temp, %op)
      assert %x eq 0
  end for
end for
end subroutine
%(smtpDriver):run(process)
end
USE Fred
DISPLAY PROCEDURE 0
</p>
==SMTP examples==
==SMTP examples==
Examples follow in which <var>Email</var> objects are used to create and send a request to an SMTP server.
Examples follow in which <var>Email</var> objects are used to create and send a request to an SMTP server.
Line 93: Line 748:
%name = 'D_Eisenhower@presidents.usa'
%name = 'D_Eisenhower@presidents.usa'
%nick = 'Ike'
%nick = 'Ike'
%letter:AddCc(%name, %nick)
%letter:AddCC(%name, %nick)
   
   
%name = L_Johnson@presidents.usa'
%name = L_Johnson@presidents.usa'
%nick = 'LBJ'
%nick = 'LBJ'
%letter:AddBcc(%name, %nick)
%letter:AddBCC(%name, %nick)
   
   
%letter:Sender('a_huckster@bankandtrustme.com')
%letter:Sender('a_huckster@bankandtrustme.com')
Line 154: Line 809:
%rc = $procopn(%procname, %procfile)
%rc = $procopn(%procname, %procfile)
if %rc ne 0 then
if %rc ne 0 then
   print 'Can''t open procedure ' with %procname    -
   print 'Can&apos;'t open procedure ' with %procname    -
     with ' in file ' with %procfile with ', rc = '  -
     with ' in file ' with %procfile with ', rc = '  -
     with %rc
     with %rc
Line 182: Line 837:


==List of Email methods==
==List of Email methods==
The [[List of Email methods|"List of Email methods"]] shows all the class methods.
The [[List of Email methods]] shows all the class methods.


==See also==
==See also==
<ul>
<ul>
<li>[[Janus Sockets User Language coding considerations]]
<li>[[Janus Sockets User Language coding considerations]]</li>
<li>[[Sample Janus Sockets programs]]
<li>[[Sample Janus Sockets programs]]</li>
<li>Socket-level interfaces:
<li>Socket-level interfaces:
<ul>
<ul>
<li>[[Janus Sockets $functions|$functions]]
<li>[[Janus Sockets $functions|$functions]]</li>
<li>[[Socket class]]
<li>[[Socket class]]</li>
</ul>
</ul></li>
 
<li>Higher-level interfaces:  
<li>Higher-level interfaces:  
<ul>
<ul>
<li>[[Email class]]
<li>[[Email class]]</li>
<li>[[HTTP Helper|HTTP Helper classes]]
<li>[[HTTP Helper|HTTP Helper classes]]</li>
<li>[[LDAP class]]
<li>[[LDAP class]]</li>
<li>[[Janus FTP Server]]
<li>[[Janus FTP Server]]</li>
<li>[[Janus Telnet Server]]
<li>[[Janus Telnet Server]]</li>
</ul></li>
</ul>
</ul>


[[Category: Janus Sockets]]  
[[Category: Janus Sockets]]  
[[Category:System classes]]
[[Category:System classes]]

Latest revision as of 22:41, 11 November 2014

The Email class is a high level, object oriented interface to client sockets that lets you write a SOUL SMTP (Simple Mail Transfer Protocol) request without knowledge of socket level programming or the format of SMTP requests and responses.

The Email class methods let a SOUL program act as an SMTP client. SMTP clients send mail requests to an SMTP server, not directly to other SMTP clients. The Email class methods only support communication with SMTP servers.

An SMTP server will "store and forward" mail it receives. This means that all messages are first received in entirety on the SMTP server, then the server attempts to deliver it to the recipients. Mail delivery can be delayed by a server that is down or too busy to accept connections. "Store and forward" gives the SMTP server the ability to retry delivery without making the SMTP client wait for completion.

A server might try to deliver a message for as long as a week or more. This, of course, makes it impractical for a client to wait for delivery confirmation, so the SMTP protocol does not provide such information. A "success" response from an SMTP server indicates only that the server has accepted the request and will attempt to deliver the mail to the recipients.

Most SMTP servers will at least confirm that any domain names in the recipient list are valid, but they cannot confirm that an actual recipient name is valid.

You must define a Janus CLSOCK port to use the Email class. If you supply the IP address of the server and the port number in the port definition, SMTP applications need only know the Janus port name to communicate with the server. The following example defines a client socket port for use by SMTP applications. For more detailed information about defining Janus client socket ports, see JANUS DEFINE and JANUS CLSOCK.

JANUS DEFINE MYSMTP * CLSOCK 5 REMOTE 198.242.244.100 25

Note: To use Email objects under versions of Model 204 earlier than 7.5, you must have licensed Janus TCP/IP Base, Janus Sockets, and Janus SOAP. Under version 7.5 and later, you do not require Janus SOAP.

For information about using SOUL objects, see Object oriented programming in SOUL and Getting started with OOP for User Language programmers.

Email class summary

The following capabilities are provided by the Email class:

  • Creating general purpose e-mail for SMTP
  • Sending general purpose e-mail via SMTP
  • Setting e-mail content using Longstrings
  • Sending MIME content (attachments and alternative content)
  • Sending to multiple recipients
  • Access to status (error or confirmation messages)

You use the Email object to generate SMTP messages (e-mail) to send to an SMTP server. To construct a request, you use Email methods to set and inspect header information, content, and attachments. To send e-mail, you use the Mail method.

List of Email methods contains a list of all the methods in the Email class.

Dummy SMTP Server example

This is an example of a very basic SMTP Server: it simply accepts Email objects from an SMTP client, and in turn, sends a response. It does not store or retrieve messages.

No Janus port definitions are shown. For comments about defining Janus ports for the Email class, see the earlier remarks.

Dummy SMTP Server

emclass.ul:

*********************************************************************** * * * encoding enumerations. * * * *********************************************************************** enumeration encoding public value base64 value none end public end enumeration *********************************************************************** * * * emailPart class. * * * *********************************************************************** class emailPart public variable headers is object stringlist variable body is object stringlist function type is string len 255 function encoding is string len 255 function name is string len 255 function contentlist is object stringlist function content is longstring end public *********************************************************************** * * * Return complete contentlist from an emailPart * * * *********************************************************************** function contentlist is object stringlist return %this:body end function *********************************************************************** * * * Decode and return content from an emailPart * * * *********************************************************************** function content is longstring %toke is object stringTokenizer %token is string len 128 %encoding is string len 128 %lf is string len 1 %i is float %j is float %content is longstring %concat is boolean initial(True) %temp is longstring if %this:body eq null then return ''; end if %lf = '0A':x for %i from %i + 1 to %this:body:count if %this:encoding eq 'base64' then %content = %content with %this:body(%i) elseif %this:encoding eq 'quoted-printable' then %temp = %this:body(%i):Unspace(Leading=False, Trailing=True, - Compress=False) if %concat = False then %content = %content %lf end if for %j from 1 to %temp:Length - 2 if %temp:Substring(%j, 1) = '=' then %content = %content %temp:Substring(%j + 1, 2): - HexToString:AsciiToEbcdic(characterEncode=true) %j = %j + 2 else %content = %content %temp:Substring(%j, 1) end if end for if %j <= %temp:Length then for %j from %j to %temp:Length %content = %content %temp:Substring(%j, 1) end for end if if %content:Right(1) = '=' then %concat = True %content = %content:TrimRight(1) else %concat = False end if else %content = %content %this:body(%i) with %lf end if end for if %this:encoding eq 'base64' then %content = %content:base64ToString if %this:type:left(5) = 'text/' then %content = %content:AsciiToEbcdic(CharacterEncode=true) end if end if return %content end function *********************************************************************** * * * Return the name (if present) of an emailPart * * * *********************************************************************** function name is string len 255 %type is string len 255 %name is string len 255 %temp is string len 255 %i is float %n is float for %i from 1 to %this:headers:count %temp = %this:headers(%i) if 'Content-Disposition:':toUpper eq %temp:word(1):toUpper then for %i from %i + 1 to %this:headers:count if %temp:word(1):right(1) eq ':' then loop end; end if %temp = %temp %this:headers(%i) end for %i = %i - 1 %n = %temp:positionOf(' filename=') if %n gt 0 then %name = %temp:subString(%n) end if loop end; end if if 'Content-Type:':toUpper eq %temp:word(1):toUpper then for %i from %i + 1 to %this:headers:count if %temp:word(1):right(1) eq ':' then loop end; end if %temp = %temp %this:headers(%i) end for %i = %i - 1 %n = %temp:positionOf(' name=') if %n gt 0 then %name = %temp:subString(%n) end if end if end for %name = %name:word(2, spaces=' =') if %name:left(1) = '"' then %name = %name:trimLeft(1); end if if %name:right(1) = '"' then %name = %name:trimRight(1); end if return %name end function *********************************************************************** * * * Return the encoding (if present) of an emailPart * * * *********************************************************************** function encoding is string len 255 %enc is string len 255 %temp is string len 255 %i is float %n is float for %i from 1 to %this:headers:count %temp = %this:headers(%i) if 'Content-Transfer-Encoding:':toUpper eq %temp:word(1):toUpper then for %i from %i + 1 to %this:headers:count if %this:headers(%i):word(1):right(1) eq ':' then loop end; end if %temp = %temp %this:headers(%i) end for loop end; end if end for if 'Content-Transfer-Encoding:':toUpper eq %temp:word(1):toUpper then %enc = %temp:word(2) if %enc:left(1) = '"' then %enc = %enc:trimLeft(1); end if if %enc:right(1) = '"' then %enc = %enc:trimRight(1); end if end if if %enc = '' then %enc = '7bit'; end if return %enc end function *********************************************************************** * * * Return the content type of an emailPart * * * *********************************************************************** function type is string len 255 %type is string len 255 %temp is string len 255 %i is float %n is float for %i from 1 to %this:headers:count %temp = %this:headers(%i) if 'Content-Type:':toUpper eq %temp:word(1):toUpper then for %i from %i + 1 to %this:headers:count if %temp:word(1):right(1) eq ':' then loop end; end if %temp = %temp %this:headers(%i) end for %i = %i - 1 loop end; end if end for %type = %temp:word(2) if %type:left(1) = '"' then %type = %type:trimLeft(1); end if if %type:right(1) = '"' then %type = %type:trimRight(1); end if if %type = '' then %type = 'text/plain'; end if return %type end function end class *********************************************************************** * * * receivedEmail class. * * * *********************************************************************** class receivedEmail public variable headers is object stringlist variable body is object stringlist variable rcptList is object stringlist variable fromHost is string len 128 variable from is string len 128 function header - (%name is string len 255, %occur is float default(1)) - is string len 255 function contentList (%x is float) is object stringlist function part (%number is float) is object emailPart function parts is float end public *********************************************************************** * * * Return a header from the email message * * * *********************************************************************** function header - (%name is string len 255, %occur is float default(1)) - is string len 255 %i is float %toke is object stringTokenizer %token is string len 255 for %i from 1 to %this:headers:count %toke = %this:headers(%i):stringTokenizer %token = %toke:nextToken:toUpper %toke:discard if %name:toUpper eq %token then loop end; end if end for if %i > %this:headers:count then return ''; end if %token = %this:headers(%i) if %this:headers(%i):right(1) ne ';' then return %token; end if for %i from %i + 1 to %this:headers:count %token = %token %this:headers(%i) if %this:headers(%i):right(1) ne ';' then loop end; end if end for return %token end function *********************************************************************** * * * Return a "part" of an email message as an emailPart object. * * * *********************************************************************** function part (%number is float) is object emailPart %i is float %n is float %bound is string len 72 %part is object emailPart %encoding is string len 255 %partNumber is float %cont is boolean initial(false) %mime is boolean initial(false) %temp is string len 255 %part = new %part:headers = new * Scan global headers for boundary for %i from 1 to %this:headers:count %temp = %this:headers(%i) if 'Content-Type:':toUpper eq %temp:word(1):toUpper then for %i from %i + 1 to %this:headers:count if %this:headers(%i):word(1):right(1) eq ':' then loop end; end if %temp = %temp %this:headers(%i) end for %n = %temp:positionOf('boundary=') if %n gt 0 then %bound = %temp:subString(%n) end if loop end; end if end for %part:headers = new %part:body = new if %bound ne '' then %bound = %bound:substring(10) if %bound:left(1) = '"' then %bound = %bound:trimLeft(1); end if if %bound:right(1) = '"' then %bound = %bound:trimRight(1); end if %bound = '--' %bound; for %i from 1 to %this:body:count if %bound = %this:body(%i) then %partNumber = %partNumber + 1 if %partNumber = %number then loop end; end if end if end for for %i from %i + 1 to %this:body:count if %this:body(%i) eq '' then loop end; end if %part:headers:add(%this:body(%i)) end for for %i from %i + 1 to %this:body:count if %bound = %this:body(%i):substring(1, %bound:length) then loop end; end if %part:body:add(%this:body(%i)) end for else assert %number eq 1 for %i from 1 to %this:headers:count %temp = %this:headers(%i) if 'Content-':toUpper eq %temp:word(1):left(8):toUpper then %part:headers:add(%temp) for %i from %i + 1 to %this:headers:count if %this:headers(%i):word(1):right(1) eq ':' then loop end; end if %part:headers:add(%this:headers(%i)) end for %i = %i - 1 end if end for for %i from 1 to %this:body:count %part:body:add(%this:body(%i)) end for end if return %part end function *********************************************************************** * * * Return number of "parts" of an email message. * * * *********************************************************************** function parts is float %i is float %n is float %bound is string len 128 %parts is float %temp is longstring * Scan global headers for boundary for %i from 1 to %this:headers:count %temp = %this:headers(%i) if 'Content-Type:':toUpper eq %temp:word(1):toUpper then for %i from %i + 1 to %this:headers:count if %this:headers(%i):word(1):right(1) eq ':' then loop end; end if %temp = %temp %this:headers(%i) end for %n = %temp:positionOf('boundary=') if %n gt 0 then %bound = %temp:subString(%n) end if loop end; end if end for if %bound ne '' then %bound = %bound:substring(10) if %bound:left(1) = '"' then %bound = %bound:trimLeft(1); end if if %bound:right(1) = '"' then %bound = %bound:trimRight(1); end if %bound = '--' %bound; else return 1 end if %parts = 0 for %i from 1 to %this:body:count if %bound = %this:body(%i) then %parts = %parts + 1 end if end for return %parts end function end class *********************************************************************** * * * smtpDriver class. * * * *********************************************************************** class smtpDriver public shared subroutine - run(%processEmail is subroutine (receivedEmail):whatever) end public shared subroutine run(%processEmail is subroutine (receivedEmail):whatever) %receivedEmail is object receivedEmail ***************************************************************** * SMTP server program. * ***************************************************************** ***************************************************************** * Static values used by the server. * ***************************************************************** %serverId is string len 80 initial - ("host.sirius-software.com running Model 204 SMTP server") ***************************************************************** * End of static values used by the server. * ***************************************************************** local subroutine (stringList):audit %i is float for %i from 1 to %this:count auditText {%i}: {%this(%i)} end for end subroutine %remail is object receivedEmail %msg is string len 128 %client is string len 128 %command is string len 16 %email is object receivedEmail %esmtp is boolean initial(false) %from is string len 128 %by is string len 128 %received is string len 255 %dateTime is string len 128 %inputLength is float %inputLine is longstring %sock is object socket %toke is object stringTokenizer %token is string len 128 %sock = serverSocket %sock:set('CHAR', 'CHAR') %sock:set('LINEND', '0D0A') %sock:set('PRSTOK', '0D0A') %dateTime = -  %(system):currentTimeString('DD Mon YYYY HH:MI:SS') %sock:sendWithLineEnd( - "220 " with %serverId with " on " with %dateTime) %inputLength = %sock:receiveAndParse(%inputLine) %toke = %inputLine:stringTokenizer %command = %toke:nextToken:toUpper if %command eq 'EHLO' then %esmtp = true elseIf %command ne 'HELO' then auditText *** Incorrect hello message received, - command={%command}, closing connection %sock:close stop end if %client = %toke:nextToken %msg = "250 Hello " with %client with - " I'm delighted to meet you" %sock:sendWithLineEnd(%msg) auditText **** Entering main SMTP loop for {%client} repeat forever %inputLength = %sock:receiveAndParse(%inputLine) %toke = %inputLine:stringTokenizer %command = %toke:nextToken:toUpper if %command eq 'QUIT' then jump to quit; end if if %command ne 'MAIL' then auditText **** Unexpected command {%command} received, - terminating connection jump to close end if %token = %toke:nextToken if %token:left(5):toUpper ne 'FROM:' then auditText **** MAIL followed by {%token} not - FROM:<email-address>, terminating connection jump to close end if %from = %token:substring(6) %sock:sendWithLineEnd("250 Ok") %email = new %email:rcptList = new %toke = %sock:info('REMOTE'):stringTokenizer %email:fromHost = %toke:nextToken repeat forever %inputLength = %sock:receiveAndParse(%inputLine) %toke = %inputLine:stringTokenizer %command = %toke:nextToken:toUpper if %command eq 'QUIT' then jump to quit; end if if %command ne 'RCPT' then loop end; end if %token = %toke:nextToken if %token:left(3):toUpper ne 'TO:' then auditText **** RCPT followed by {%token} not - TO:<email-address>, terminating connection jump to close end if %email:rcptList:add(%token:substring(4)) %sock:sendWithLineEnd("250 Ok") end repeat if %email:rcptList:count eq 0 then auditText **** No RCPT TO:s received after a - MAIL FROM:, terminating connection jump to close end if if %command ne 'DATA' then auditText **** No DATA received after a RCPT TO:, - terminating connection jump to close end if %sock:sendWithLineEnd("354 End data with a single '.'") %email:headers = new setText %received = Received: from {%email:fromHost} by - {%serverid} ; - {%(system):currentTimeString('Wkd, DD Mon YYYY HH:MI:SS')} %email:headers:add(%received) repeat forever %inputLength = %sock:receiveAndParse(%inputLine) if %inputLine = '' then loop end; end if if %inputLine eq '.' then loop end; end if if %inputLine:left(2) eq '..' then %inputLine = %inputLine:substring(2); end if %email:headers:add(%inputLine) end repeat %email:body = new repeat forever %inputLength = %sock:receiveAndParse(%inputLine) if %inputLine eq '.' then loop end; end if if %inputLine:left(2) eq '..' then %inputLine = %inputLine:substring(2); end if %email:body:add(%inputLine) end repeat %sock:sendWithLineEnd("250 E-mail received") end repeat quit: auditText **** QUIT received %sock:sendWithLineEnd("221 Bye") close: auditText **** Terminating SMTP connection %sock:close %email:%processEmail end subroutine end class

driver.ul:

UTABLE LNTBL 128 b i emclass.ul local subroutine (receivedEmail):process %i is float %j is float %x is float %ls is longstring %op is string len 8 %temp is string len 255 %ename is string len 255 %part is object emailPart %tp is float %tp = 0 setText %ename = CSURF_EMAIL_{$sir_date('YYMMDDHHMISS')} %x = $bldproc(%tp, %ename, 'OPEN') assert %x eq 0 * * Get attachments and copy content to output procedure * %op = 'APPEND' for %i from 1 to %this:parts %part = %this:part(%i) for %j from 1 to %part:headers:count %temp = %part:headers(%j):left(255) %x = $bldproc(%tp, %temp, %op) assert %x eq 0 end for for %j from 1 to %part:body:count %temp = %part:body(%j):left(255) %x = $bldproc(%tp, %temp, %op) assert %x eq 0 end for end for end subroutine %(smtpDriver):run(process) end USE Fred DISPLAY PROCEDURE 0

SMTP examples

Examples follow in which Email objects are used to create and send a request to an SMTP server. No Janus port definitions are shown. For comments about defining Janus ports for the Email class, see the earlier remarks.

Basic e-mail example

This example uses most of the Email methods to send a simple message to three recipients. The local CLSOCK port used is named SPAM.

b %letter object Email %h longstring %t longstring %name longstring %nick string len 64 %letter = new %name = 'C_Coolidge@presidents.usa' %nick = 'SilentCal' %letter:AddRecipient(%name, %nick) %name = 'D_Eisenhower@presidents.usa' %nick = 'Ike' %letter:AddCC(%name, %nick) %name = L_Johnson@presidents.usa' %nick = 'LBJ' %letter:AddBCC(%name, %nick) %letter:Sender('a_huckster@bankandtrustme.com') %h = 'Subject:' %t = 'Lower Mortgage Rates' %letter:AddHeader(%h, %t) %h = 'Great deal of debt for you!' %letter:SetBody(%h) %letter:mail('SPAM', 0) print %letter:GetReplyCode with ' - ' with - %letter:GetReplyText %letter:discard end

E-mail with attachment

In the following example, an image is attached to a short e-mail message. The attachment is produced from a member of a local Model 204 procedure file using Stringlist class methods.

b %rolex is object Email %sl is object Stringlist %procdata is object Stringlist %jpg longstring %t longstring %temp string len 255 %procfile string len 10 %procname string len 63 %q float %rc float %rolex = new %sl = new %t = 'jbgood@netscape.net' %rolex:AddRecipient(%t, 'JB') %t = 'cberry@ucantbee-sirius.com' %rolex:Sender(%t) %rolex:AddHeader('Subject:', 'watch this...') text to %sl I want an authentic imitation Rolex watch. Something like the one shown in this image. Know anyone who's got one? end text %rolex:SetBody(%sl, encoding='none', type='text/plain') %procfile = 'MYPROC' %procname = 'FAKE5.JPG' %rc = $procopn(%procname, %procfile) if %rc ne 0 then print 'Can''t open procedure ' with %procname - with ' in file ' with %procfile with ', rc = ' - with %rc stop end if %procdata = new %procdata:appendOpenProcedure %jpg = %procData:binaryProcedureDecode %rolex:AddPart(%jpg, type='image/jpg', name='Rolex1.jpg') print %rolex:mail('MYSMTP', 0) ' is return from Mail method' print %rolex:GetReplyCode ' is return from GetReplyCode' print %rolex:GetReplyText %rolex:discard end

If the request is successful, results like the following display at the terminal:

0 is return from Mail method 221 is return from GetReplyCode ucantbee-sirius.com running IBM VM SMTP Level 320 closing connection

List of Email methods

The List of Email methods shows all the class methods.

See also