B4J Tutorial Building a mini "Email based server"

This example uses jNet library together with the MailParser module to build a program that responds to emails.

SS-2013-11-27_10.48.58.png


Although it sounds a bit strange, using emails as the communication channel can be very simple and useful.

You do not need to worry about hosting, firewall or (partially) downtime issues. Let Google, Yahoo, Microsoft or one of the other email providers solve these issues for you.

This program checks an email account every two minutes. If it finds a message it downloads it, parses it and deletes it.

In this case the "server" doesn't do anything useful. It checks the subject line. If the value is 13 then it sends a reply with the current time.

A client (B4A) program can use SMTP from the Net library to send emails to this server.

You can for example send database updates as an attachment and insert the data to a database. Or any other creative solution you can think of...
 

Attachments

  • EmailServer.zip
    3.5 KB · Views: 2,080

George Popovic

Member
Licensed User
Longtime User
I tried this program with correct Gmail account and password but get this message:

12/06/2013 10:13:28: Error: (SocketException) java.net.SocketException: Network is unreachable: connect
This example uses jNet library together with the MailParser module to build a program that responds to emails.

SS-2013-11-27_10.48.58.png


Although it sounds a bit strange, using emails as the communication channel can be very simple and useful.

You do not need to worry about hosting, firewall or (partially) downtime issues. Let Google, Yahoo, Microsoft or one of the other email providers solve these issues for you.

This program checks an email account every two minutes. If it finds a message it downloads it, parses it and deletes it.

In this case the "server" doesn't do anything useful. It checks the subject line. If the value is 13 then it sends a reply with the current time.

A client (B4A) program can use SMTP from the Net library to send emails to this server.

You can for example send database updates as an attachment and insert the data to a database. Or any other creative solution you can think of...
 

pesquera

Active Member
Licensed User
Longtime User
Hi Erel, is possible to do an email server from the Android side? like the B4J desktop example, but with B4A
trying to intercept emails like that example does
 

Theera

Well-Known Member
Licensed User
Longtime User
Hi all,
I try to change PopServer="pop.hotmail.com" and StmpServer="stmp.hotmail.com" ,I 've message "01/18/2014 09:22:48: Error: (UnknownHostException) java.net.UnknownHostException: pop.hotmail.com" How do I set?
 

Christos Dorotheou

Member
Licensed User
Longtime User
Dear All,

I've wrote an application based on Erel's EmailServer example which receives incoming Emails that have pictures as attachments and stores
those pictures into a structured folder on a Server and various Email Details into an Access Database.

Everything works perfectly except in those cases where the Sender of the Email uses Greek language in the Mail's Subject, at which case the
Subject text in the Header of the Email is encoded as =?iso-8859-7 and becomes unreadable.

Is there any way to encode the subject text back to Greek?

Any help it will be most welcome

Regards Christos
 

Christos Dorotheou

Member
Licensed User
Longtime User
Erel,

I have read the 'encoding specification' you have recommended and it helped.

I have come up with a solution for my problem with a subroutine that I wrote,
which accepts the Raw Subject Data extracted from the Email and returns it back as a readable string.

Because I am not sure if pasting the code here, so people with similar problem can use it, is allowed,
I would be glad to post it to anyone on request.


Thanks for your help.
 

Christos Dorotheou

Member
Licensed User
Longtime User
These are the 2 Subroutines I have come up with to handle encoded text contained in a MIME email header

DecodeMIMEText(strParam) will accept a string which may contain a MIME encoded string and will return a decoded string
as in the case of the From: field in the Header where the name of the Sender is encoded and address is not.

From: =?ISO-8859-7?B?wtzz7/Ig0+nv/fb04fI=?= <[email protected]>

Example

Log(DecodeMIMEText("=?ISO-8859-7?B?wtzz7/Ig0+nv/fb04fI=?= <[email protected]>"))


B4X:
'(B for Base64, Q for Quoted-Printable)

Sub DecodeMIMEText(EncodedText As String) As String
Dim CharSet As String
Dim Buffer() As Byte
Dim SupportedEncodings() As String
Dim result As String
Dim Part As String
Dim cnv As ByteConverter
Dim sbj_sb As StringBuilder
Dim s_u As StringUtils
Dim k, j, x As Int
Dim booCharSetOK, HasMIMECoding, ValidMIME As Boolean
Dim MIMEStart, MIMEEnd As Int
Dim MIMEPart As String
Dim NONMIMEPart1 As String
Dim NONMIMEPart2 As String

sbj_sb.Initialize
SupportedEncodings = cnv.SupportedEncodings

MIMEStart = 0
MIMEEnd = 0
HasMIMECoding = False

MIMEStart = EncodedText.Trim.IndexOf("=?")
MIMEEnd = EncodedText.Trim.IndexOf("?=")

If MIMEEnd >=0 Then
  MIMEEnd = MIMEEnd + 2 'To account for '?=' when will be be passed later through SubSustring Function
End If

'(B for Base64, Q for Quoted-Printable)
If EncodedText.Trim.IndexOf("?B?") >= 0 OR EncodedText.Trim.IndexOf("?Q?") >= 0 Then
  HasMIMECoding = True
Else
  HasMIMECoding = False
End If

If MIMEStart >= 0 AND MIMEEnd > MIMEStart AND HasMIMECoding = True Then
  ValidMIME = True
 
  If MIMEStart > 0 Then
      NONMIMEPart1 = EncodedText.Trim.SubString2(0,MIMEStart)
  Else
      NONMIMEPart1 = ""
  End If
 
  MIMEPart = EncodedText.Trim.SubString2(MIMEStart,MIMEEnd)

  If MIMEEnd < EncodedText.Trim.Length Then
      NONMIMEPart2 = EncodedText.Trim.SubString(MIMEEnd)
  Else
      NONMIMEPart2 = ""
  End If 
Else
  ValidMIME = False
  NONMIMEPart1 = EncodedText
  MIMEPart = ""
  NONMIMEPart2 = ""
End If

If ValidMIME = True Then
    'Check if String passed as parameter is MIME encoded
    If MIMEPart.Length > 0 Then
      'Check if MIME coding used is "B" (B for Base64, Q for Quoted-Printable)
      If MIMEPart.Trim.Contains("?B?") = True Then
          'Retrieve Character Set Used for Coding
          CharSet = MIMEPart.Trim.SubString2(0, MIMEPart.Trim.IndexOf("?B?"))
          If CharSet.Trim.StartsWith("=?") = True Then
            CharSet = CharSet.Trim.SubString(2)
          End If
         
          booCharSetOK = False 'Check if Character Set of Header is supported by the system
          For j = 0 To SupportedEncodings.Length - 1
              If CharSet.ToUpperCase = SupportedEncodings(j).ToUpperCase Then
                booCharSetOK = True
              End If       
          Next
         
          If booCharSetOK = True Then
            Part = MIMEPart.Trim.SubString2(MIMEPart.Trim.IndexOf("?B?")+3,MIMEPart.Trim.Length-2)
            Try
              Buffer = s_u.DecodeBase64(Part)
              'result = BytesToString(Buffer, 0, Buffer.Length, CharSet)
              result = cnv.StringFromBytes(Buffer, CharSet)
            Catch
              result = MIMEPart
            End Try
            sbj_sb.Append(result.Trim)
          Else
            sbj_sb.Append(MIMEPart) 'Since Header's Character Set is not supported by the system, return it back asis
          End If
         
      'Check if MIME coding used is "Q" (B for Base64, Q for Quoted-Printable)
      Else If MIMEPart.Contains("?Q?") = True Then
          Dim WordsBuffer() As String
          Dim CharsBuffer() As String

          'Retrieve Character Set Used for Coding
          CharSet = MIMEPart.Trim.SubString2(0, MIMEPart.Trim.IndexOf("?Q?"))
          If CharSet.Trim.StartsWith("=?") = True Then
            CharSet = CharSet.Trim.SubString(2)
          End If
         
          booCharSetOK = False 'Check if Character Set of Header is supported by the system
          For j = 0 To SupportedEncodings.Length - 1
              If CharSet.ToUpperCase = SupportedEncodings(j).ToUpperCase Then
                booCharSetOK = True
              End If       
          Next
         
          If booCharSetOK = True Then
            Part = MIMEPart.Trim.SubString2(MIMEPart.Trim.IndexOf("?Q?")+3,MIMEPart.Trim.Length-2).Trim
            'Split string to Words
            WordsBuffer=Regex.Split("_", Part)
            For k = 0 To WordsBuffer.Length - 1
                If WordsBuffer(k).Contains("=") = True Then
                    'Split Word to Characters
                    CharsBuffer=Regex.Split("=", WordsBuffer(k))
                    For x = 0 To CharsBuffer.Length - 1
                        Try
                          Buffer = cnv.HexToBytes(CharsBuffer(x))
                          'sbj_sb.Append(cnv.StringFromBytes(Buffer,"WINDOWS-1253").Trim)
                          sbj_sb.Append(cnv.StringFromBytes(Buffer,CharSet).Trim)
                        Catch
                          sbj_sb.Append(CharsBuffer(x))
                        End Try
                    Next
                Else
                    sbj_sb.Append(WordsBuffer(k))
                End If
                sbj_sb.Append(" ")
            Next
          Else
            sbj_sb.Append(MIMEPart) 'Since Header's Character Set is not supported by the system, return it back asis
          End If
      Else
          sbj_sb.Append(MIMEPart) 'Header is not Encoded or Wrongly Encoded therefor send it back asis
      End If     
    Else
      sbj_sb.Append(MIMEPart) 'Header is not Encoded or Wrongly Encoded therefor send it back asis
    End If

    Return NONMIMEPart1 & sbj_sb.ToString & NONMIMEPart2
Else
    Return NONMIMEPart1 & MIMEPart & NONMIMEPart2
End If

End Sub



DecodeMIMEArray(arrParam) will accept String Array with MIME encoded string elements and will return a decoded string
as in the case of the Subject: field in the Header where the subject may be in multiple encoded lines.

Subject: =?utf-8?B?zpTPhc+Dz4TPjc+HzrfOvM6xIEtCQTEyMyDPg8+EzrfOvSDOn860z4wgzpzOsQ==?=
=?utf-8?B?zrrOsc+Bzq/Ov8+FIM+Dz4TOuc+CIDMwLzAxLzIwMTQ=?=


or

Subject: =?iso-8859-7?Q?=C1=F4=FD=F7=E7=EC=E1_=F3=F4=E9=F2_31/01/2014_=F3=F4=E7=ED?=
=?iso-8859-7?B?IMvl+fb88e8gwfH34ePj3evv9SDM6ffh3ussIMvl9er589/hLg==?=


Example

Dim Test(2) as string

Test(0) = "?utf-8?B?zpTPhc+Dz4TPjc+HzrfOvM6xIEtCQTEyMyDPg8+EzrfOvSDOn860z4wgzpzOsQ==?="
Test(1) = "=?utf-8?B?zrrOsc+Bzq/Ov8+FIM+Dz4TOuc+CIDMwLzAxLzIwMTQ=?="

Log(DecodeMIMEArray(Test))


B4X:
Sub DecodeMIMEArray(EndcodedStringParts() As String) As String
Dim CharSet As String
Dim Buffer() As Byte
Dim SupportedEncodings() As String
Dim result As String
Dim Part As String
Dim cnv As ByteConverter
Dim sbj_sb As StringBuilder
Dim s_u As StringUtils
Dim i, k, j, x As Int
Dim booCharSetOK As Boolean

sbj_sb.Initialize
SupportedEncodings = cnv.SupportedEncodings

For i = 0 To EndcodedStringParts.Length-1
    'Check if String passed as parameter is MIME encoded
    If EndcodedStringParts(i).Trim.StartsWith("=?") = True AND EndcodedStringParts(i).Trim.EndsWith("?=") = True Then
      'Check if MIME coding used is "B" (B for Base64, Q for Quoted-Printable)
      If EndcodedStringParts(i).Trim.Contains("?B?") = True Then
          'Retrieve Character Set Used for Coding
          CharSet = EndcodedStringParts(i).Trim.SubString2(0, EndcodedStringParts(i).Trim.IndexOf("?B?"))
          If CharSet.Trim.StartsWith("=?") = True Then
            CharSet = CharSet.Trim.SubString(2)
          End If
         
          booCharSetOK = False 'Check if Character Set of Header is supported by the system
          For j = 0 To SupportedEncodings.Length - 1
              If CharSet.ToUpperCase = SupportedEncodings(j).ToUpperCase Then
                booCharSetOK = True
              End If       
          Next
         
          If booCharSetOK = True Then
            Part = EndcodedStringParts(i).Trim.SubString2(EndcodedStringParts(i).Trim.IndexOf("?B?")+3,EndcodedStringParts(i).Trim.Length-2)
            Try
              Buffer = s_u.DecodeBase64(Part)
              'result = BytesToString(Buffer, 0, Buffer.Length, CharSet)
              result = cnv.StringFromBytes(Buffer, CharSet)
            Catch
              result = EndcodedStringParts(i)
            End Try
            sbj_sb.Append(result.Trim)
          Else
            sbj_sb.Append(EndcodedStringParts(i)) 'Since Header's Character Set is not supported by the system, return it back asis
          End If
         
      'Check if MIME coding used is "Q" (B for Base64, Q for Quoted-Printable)
      Else If EndcodedStringParts(i).Contains("?Q?") = True Then
          Dim WordsBuffer() As String
          Dim CharsBuffer() As String

          'Retrieve Character Set Used for Coding
          CharSet = EndcodedStringParts(i).Trim.SubString2(0, EndcodedStringParts(i).Trim.IndexOf("?Q?"))
          If CharSet.Trim.StartsWith("=?") = True Then
            CharSet = CharSet.Trim.SubString(2)
          End If
         
          booCharSetOK = False 'Check if Character Set of Header is supported by the system
          For j = 0 To SupportedEncodings.Length - 1
              If CharSet.ToUpperCase = SupportedEncodings(j).ToUpperCase Then
                booCharSetOK = True
              End If       
          Next
         
          If booCharSetOK = True Then
            Part = EndcodedStringParts(i).Trim.SubString2(EndcodedStringParts(i).Trim.IndexOf("?Q?")+3,EndcodedStringParts(i).Trim.Length-2).Trim
            'Split string to Words
            WordsBuffer=Regex.Split("_", Part)
            For k = 0 To WordsBuffer.Length - 1
                If WordsBuffer(k).Contains("=") = True Then
                    'Split Word to Characters
                    CharsBuffer=Regex.Split("=", WordsBuffer(k))
                    For x = 0 To CharsBuffer.Length - 1
                        Try
                          Buffer = cnv.HexToBytes(CharsBuffer(x))
                          'sbj_sb.Append(cnv.StringFromBytes(Buffer,"WINDOWS-1253").Trim)
                          sbj_sb.Append(cnv.StringFromBytes(Buffer,CharSet).Trim)
                        Catch
                          sbj_sb.Append(CharsBuffer(x))
                        End Try
                    Next
                Else
                    sbj_sb.Append(WordsBuffer(k))
                End If
                sbj_sb.Append(" ")
            Next
          Else
            sbj_sb.Append(EndcodedStringParts(i)) 'Since Header's Character Set is not supported by the system, return it back asis
          End If
      Else
          sbj_sb.Append(EndcodedStringParts(i)) 'Header is not Encoded or Wrongly Encoded therefor send it back asis
      End If     
    Else
      sbj_sb.Append(EndcodedStringParts(i)) 'Header is not Encoded or Wrongly Encoded therefor send it back asis
    End If
Next

Return sbj_sb.ToString

End Sub

Any suggestions or improvements are welcome.

Chris
 

Christos Dorotheou

Member
Licensed User
Longtime User
This example uses jNet library together with the MailParser module to build a program that responds to emails.

SS-2013-11-27_10.48.58.png


Although it sounds a bit strange, using emails as the communication channel can be very simple and useful.

You do not need to worry about hosting, firewall or (partially) downtime issues. Let Google, Yahoo, Microsoft or one of the other email providers solve these issues for you.

This program checks an email account every two minutes. If it finds a message it downloads it, parses it and deletes it.

In this case the "server" doesn't do anything useful. It checks the subject line. If the value is 13 then it sends a reply with the current time.

A client (B4A) program can use SMTP from the Net library to send emails to this server.

You can for example send database updates as an attachment and insert the data to a database. Or any other creative solution you can think of...

I recommend to disable the timer while checking for new mail and while attachments are still been downloaded and processed, because if there
is a significant amount of emails with attachments in queue then the timer will fire again before the whole process is over.

You can disable the timer from within the MailTimer_Tick Subroutine as shown bellow:

B4X:
Sub MailTimer_Tick
    LogMessage("[SCM] Start Checking Mail on " & DateTime.Date(DateTime.Now))

    mailTimer.Interval = 0
    mailTimer.Enabled = False ' Disable timer while checking for new mail
   
    pop.Initialize(popServer, popPort, user, password, "pop")
   
    pop.UseSSL = True
    pop.ListMessages
   
    progress1.Visible = True
End Sub

And Re-Enable the timer from within POP_DownloadCompleted subroutine as follows:

B4X:
Sub POP_DownloadCompleted (Success As Boolean, MessageId As Int, MessageText As String)
    If Success = False Then
        LogMessage(LastException.Message)
    Else
        'Parse the mail
        Dim m As Message
        m = MailParser.ParseMail(MessageText, strAttFolder)
        HandleMessage(m)
    End If
   
    If MessageId = lastMsgId Then
      progress1.Visible = False
      LogMessage("[ECM] End Checking Mail on " & DateTime.Date(DateTime.Now))
     
      mailTimer.Initialize("mailTimer", PolingFreq)
      mailTimer.Enabled = True ' Re-Enable timer after finished checking for new mail
    End If 
   
End Sub
 

manios

Active Member
Licensed User
Longtime User
Hi all,
I receive mails coded "Content-Type: text/plain; charset=utf-8", however the body I get from the mailparser is not decoded correctly.

Instead of "Hückelhoven" I get "H=C3=BCckelhoven". Did I overlook something, do I need to run an extra decode?

Manios
 

manios

Active Member
Licensed User
Longtime User
MailParser doesn't do anything to the body. It seems like the text was not encoded correctly.
Thanks, will have to check with the sender!
 

Christos Dorotheou

Member
Licensed User
Longtime User
Hi all,
I receive mails coded "Content-Type: text/plain; charset=utf-8", however the body I get from the mailparser is not decoded correctly.

Instead of "Hückelhoven" I get "H=C3=BCckelhoven". Did I overlook something, do I need to run an extra decode?

Manios

This routine should work:

Example:

Log(UTF2UNI("H=C3=BCckelhoven","UTF-8"))

Returns: Hückelhoven

B4X:
Sub UTF2UNI(s2match As String, CharSet As String) As String
Dim m As Matcher
Dim Parts As List
Dim cnv As ByteConverter
Dim i As Int
Dim i2s As String

Parts.Initialize

m = Regex.Matcher("=[0-9a-fA-F][0-9a-fA-F]=[0-9a-fA-F][0-9a-fA-F]",s2match)

i = -1
Do While m.Find = True
  i = i + 1
  i2s = i
  Parts.Add(m.Group(0).Replace("=",""))
  s2match = s2match.Replace(m.Group(0),"~" & i2s)
  m = Regex.Matcher("=[0-9a-fA-F][0-9a-fA-F]=[0-9a-fA-F][0-9a-fA-F]",s2match)
Loop

If Parts.Size > 0 Then
  For i = 0 To Parts.Size - 1
    i2s = i
    s2match = s2match.Replace("~" & i2s, cnv.StringFromBytes(cnv.HexToBytes(Parts.Get(i)), CharSet))
  Next
End If

Return s2match

End Sub
 
Last edited:

manios

Active Member
Licensed User
Longtime User
This routine should work:

Example:

Log(UTF2UNI("H=C3=BCckelhoven","UTF-8"))

Returns: Hückelhoven
Hi Christos,
Thanks for the Sub. It helps and works OK.
However something is still wrong with the coding of the mails or the decoding. I get intermittend some "=" and "=20" characters/strings in the message body from the Mailparser. This finally screws up the conversion and contents.
I will try to get the raw contents of the mail with an old VB program and see what is wrong
 
Top