Email attachments from arduino as an SMTP client

Finally, what's the bottleneck in this system? Ethernet is really fast so why would sending a 50KB picture take 30 seconds?

Not ethernet on the Arduino. Besides, this thread started with sending the picture using a cell phone module.

Oh cool, thanks Paul!

primate:
What will probably happen is that we end up with everyone using all 00-00-00-00-00-00 as their MAC. I wonder if it might be an issue if you have multiple devices with the same MAC address on the same subnet though.

'Official' Ethernet shield comes with a sticker with an unique MAC written. It starts with 90-A2-DA-
The problem with multiple Arduinos is the same hostname "WIZnet", but it can be changed in the Ethernet library (Dhcp.h).

primate:
Also, does the Ethernet shield have existing libraries for DNS lookup and all that?

Yeah. I'm usign Arduino 1.0-beta1, which comes with DNS and DHCP support.

primate:
Finally, what's the bottleneck in this system? Ethernet is really fast so why would sending a 50KB picture take 30 seconds?.

Getting an IP (~4 seconds), reading each byte from the SD (~40 microseconds each: 6 s), SMTP traffic and load.... don't know exactly.

PaulS:
Besides, this thread started with sending the picture using a cell phone module.

? O_o

Sairam:
I am aware of the fact that it is possible to send emails to the SMTP server using the arduino ethernet shield. I want to send picture attachments along with the mail.

can you use the code above that razor blade posted above for txt files?

i placed the code with .txt instead of jpgs

but when i run it i get this

connected
220 to5email2.gprs.rogers.com ESMTP server (InterMail vM.7.09.00.04 201-2219-102-106-20090410) ready Fri, 12 Aug 2011 13:28:53 -0400
250 to5email2.gprs.rogers.com
250 Sender camarduino@gmx.com Ok
250 Recipient cam_the_man007@hotmail.com Ok
500 Command unknown: 'CONTENT-TYPE:;'
500 Command unknown: 'CONTENT-DISPOSITION:'
500 Command unknown: 'CONTENT-TRANSFER-ENCODING:'
500 Command unknown: ''
500 Command unknown: 'BWVVDYBTZW93IG1LBW=='
500 Command unknown: '.'
221 to5email2.gprs.rogers.com ESMTP server closing connection

any insight?

If I remember correctly, I believe any Headers (starting from CONTENT-TYPE) is actually part of the mesage content, and needs to come after the DATA command - but I must admit I'm a bit rusty on this.

Seems like you are only printing the responses from the server. It would help diagnostics if we can also see the outbound SMTP commands to the server too.

This is the code i am running with the headers after data, which sends an email but no attachment just places those lines of code in the email body.

the results i posted above is when the "DATA" line is removed and the subject

#include <Ethernet.h>
#include <SPI.h>
#include <SD.h>



byte mac[] = { 0, 0, 0, 0, 0, 0 }; // Arduino's artificial mac address
byte ip[] = { 0, 0, 0, 0 }; // my ip
byte server[] = { 0, 0, 0, 0 }; // my smtp server ip
int time = 5000;
int wait = 2000;

static const char cb64[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

File datatxt;
Client client(server, 25);


void setup()
{
 delay(time);
 
 Ethernet.begin(mac, ip);
 Serial.begin(9600);
 delay(1000);
 
 pinMode(10,OUTPUT); 
 SD.begin(4);
 
 Serial.println("connecting...");
 
 if (client.connect()) {
                        
                           
                        Serial.println("connected");
   
                        client.println("HELO itismeletschat"); /* say hello (statement after helo is needed but irrelevant)*/
                          delay(wait); /* wait for a response */

                        client.println("MAIL From: camarduino@gmx.com"); /* identify sender, this should be the same as the smtp server you are using */
                          delay(wait); /* wait for a response */

                        client.println("RCPT To: cameroncollie@gmail.com"); /* identify recipient */
                        delay(wait); /* wait for a response */

                        client.println("DATA");
                          delay(wait); /* wait for a response */
                        
                        datatxt =SD.open("data.txt",FILE_READ);
                        client.println("Subject: Picture attached");
                        client.print("Content-Type: txt; name=\"data.txt\"\r\nContent-Disposition: attachment; filename=\"data.txt\"\r\nContent-Transfer-Encoding: base64\r\n\r\n");
                        
                        client.println("."); /* end email */
                 
                        client.println("QUIT"); /* terminate connection */
                        delay(wait); /* wait for a response */
                        client.stop();
                        datatxt.close();
                        
  
 } else {
   Serial.println("connection failed");
 }
}

void loop()
{
 while (client.available()) {
   char c = client.read();
   Serial.print(c);
 }
 
 if (!client.connected()) {
   Serial.println();
   Serial.println("disconnecting.");
   client.stop();
   for(;;)
     ;
 }
}




void encodeblock(unsigned char in[3],unsigned char out[4],int len) {
  out[0]=cb64[in[0]>>2]; out[1]=cb64[((in[0]&0x03)<<4)|((in[1]&0xF0)>>4)];
  out[2]=(unsigned char) (len>1 ? cb64[((in[1]&0x0F)<<2)|((in[2]&0xC0)>>6)] : '=');
  out[3]=(unsigned char) (len>2 ? cb64[in[2]&0x3F] : '=');
}

void encode() {
  unsigned char in[3],out[4]; 
  int i,len,blocksout=0;
  
  while (datatxt.available()!=0) {
    len=0; 
      for (i=0;i<3;i++){ 
            in[i]=(unsigned char) datatxt.read(); 
                if (datatxt.available()!=0) len++; 
                      else in[i]=0;
      }
      if (len){
          encodeblock(in,out,len); 
           for(i=0;i<4;i++) client.write(out[i]); 
                blocksout++; }
      if (blocksout>=19||datatxt.available()==0){ 
          if (blocksout) client.print("\r\n");  blocksout=0; 
      }
   }
}

Hmmm, the only thing I can think of is that \r might not be an allowed character in the SMTP protocol, so instead of \r\n (which is CR-LF, where CR is only relevant for screen display), you can use \n only. Try breaking down the combined lines in the one print() call into separate println() commands and you might be able get more meaningful diagnostics.

Just my 2 cents' worth.

Well i just tried that code again,
it decided to work this time, now i just need to send the contents of the txt file, it didn't have any contents.
but i can send a txt file.

oh! nevermind, i missed calling the function "encode();"

Everything is good. Thanks

If i want to send multiple attachments, do i have to use boundaries?
which i guess the next question is, what are boundaries and how do i use them?

okay i figured that out too, here's a post Incase someone is wondering.
Sending Multiple Attachments

     Serial.println("connected");
   
                        client.println("HELO itismeletschat"); /* say hello (statement after helo is needed but irrelevant)*/
                          delay(wait); /* wait for a response */

                        client.println("MAIL From: blank@gmx.com"); /* identify sender, this should be the same as the smtp server you are using */
                          delay(wait); /* wait for a response */

                        client.println("RCPT To: blank@virelec.com"); /* identify recipient */
                          delay(wait); /* wait for a response */

                        client.println("DATA");
                          delay(wait); /* wait for a response */
                        
                        client.println("Subject: text file attached");
                        
                        [b]client.print("Content-type: multipart/mixed; boundary=\"$boundary\" \r\n");[/b]
                        
                        [b]client.print("--$boundary\r\n");[/b]
                        
                        datatxt =SD.open("HEAD2.csv",FILE_READ);                 
                        client.print("Content-Type: csv; name=\"HEAD2.csv\"\r\nContent-Disposition: attachment; filename=\"HEAD2.csv\"\r\nContent-Transfer-Encoding: base64\r\n\r\n");
                        encode();
                        datatxt.close();
                        delay(wait);
                        
                        datatxt =SD.open("meow.txt",FILE_READ);
                        [b]client.print("--$boundary\r\n");[/b]
                        client.print("Content-Type: txt; name=\"meow.txt\"\r\nContent-Disposition: attachment; filename=\"meow.txt\"\r\nContent-Transfer-Encoding: base64\r\n\r\n");
                        encode();
                        datatxt.close();
                        
                        client.println("."); /* end email */
                        client.println("QUIT"); /* terminate connection */
                        delay(wait); /* wait for a response */
delay(wait); /* wait for a response */

If you are waiting for a response, shouldn't you then read the response? Or else remove the incorrect comment. Even better, remove the useless delay() calls.

PaulS:

delay(wait); /* wait for a response */

If you are waiting for a response, shouldn't you then read the response? Or else remove the incorrect comment. Even better, remove the useless delay() calls.

Some [older] SMTP servers don't support pipelining (i.e. receiving a new command before the current command is responded to), and even for servers that support pipelining, some commands are in herently not pipelineable, so sending commands in quick succession without a bit of delay might be problematic (especially if the sketch is not handling server errors. It might be safer to keep some sort of delay.

Not reading the server response should not be a big deal and the receive buffer should just fill up with unread responses. Downside is of course it can't process server errors. I'm not sure how the Serial stack on the Arduino handles full buffers but if it follows general parctice it will just silently drop the new bytes.

I think the safer way (if reliability is an issue), is to replace the constant wait with a function call that loops to wait for client.available() > 0, and just read and discard the bytes beefore sending the next command. This ensures the server had responded, and to account for transient network delays that causes replies to come back more than the constant wait period.

I realized that I might be getting off topic here ...

I realized that I might be getting off topic here ...

No, I think you are properly discussing the actions that need to be performed, instead of just blindly waiting what might be just enough time, not near enough time, or way too long.

Hi,
So testing the above code, I still cannot get any "attachment" to my email.

  client.println("Subject: Arduino attachment email test\r\n");
  picture = SD.open("data.txt", FILE_READ);
  client.print("Content-Type: txt; name=\"data.txt\"\r\nContent-Disposition: attachment; filename=\"data.txt\"\r\nContent-Transfer-Encoding: base64\r\n\r\n");
  encode();
  client.println("."); /* end email */

  if(!eRcv()) return 0;

  Serial.println(F("Sending QUIT"));
  strcpy_P(tBuf,PSTR("QUIT\r\n"));  
  client.write(tBuf);
  if(!eRcv()) return 0;

  client.stop();

  Serial.println(F("disconnected"));

  return 1;
}


void encodeblock(unsigned char in[3], unsigned char out[4], int len) {
  out[0] = cb64[in[0] >> 2]; out[1] = cb64[((in[0] & 0x03) << 4) | ((in[1] & 0xF0) >> 4)];
  out[2] = (unsigned char) (len > 1 ? cb64[((in[1] & 0x0F) << 2) | ((in[2] & 0xC0) >> 6)] : '=');
  out[3] = (unsigned char) (len > 2 ? cb64[in[2] & 0x3F] : '=');
}
void encode() {
  unsigned char in[3], out[4]; int i, len, blocksout = 0;
  while (picture.available() != 0) {
    len = 0; for (i = 0; i < 3; i++) {
      in[i] = (unsigned char) picture.read();
      if (picture.available() != 0) len++;
      else in[i] = 0;
    }
    if (len) {
      encodeblock(in, out, len);
      for (i = 0; i < 4; i++) client.write(out[i]);
      blocksout++;
    }
    if (blocksout >= 19 || picture.available() == 0) {
      if (blocksout) client.print("\r\n");
      blocksout = 0;
    }
  }
}

This code just inserts some garbage characters on my email. It should ideally attach the file data.txt

.....
 client.print("Content-Type: txt; name=\"data.txt\"\r\nContent-Disposition: attachment; filename=\"data.txt\"\r\nContent-Transfer-Encoding: base64\r\n\r\n");
 encode();
 client.print("\r\n.\r\nQUIT\n");
 client.println("."); /* end email */
 client.stop();
 datatxt.close();
 while(1);
}



void encodeblock(unsigned char in[3],unsigned char out[4],int len) {
 out[0]=cb64[in[0]>>2]; out[1]=cb64[((in[0]&0x03)<<4)|((in[1]&0xF0)>>4)];
 out[2]=(unsigned char) (len>1 ? cb64[((in[1]&0x0F)<<2)|((in[2]&0xC0)>>6)] : '=');
 out[3]=(unsigned char) (len>2 ? cb64[in[2]&0x3F] : '=');
}

void encode() {
 unsigned char in[3],out[4];
 int i,len,blocksout=0;
 
 while (datatxt.available()!=0) {
   len=0;
     for (i=0;i<3;i++){
           in[i]=(unsigned char) datatxt.read();
               if (datatxt.available()!=0) len++;
                     else in[i]=0;
     }
     if (len){
         encodeblock(in,out,len);
          for(i=0;i<4;i++) client.write(out[i]);
               blocksout++; }
     if (blocksout>=19||datatxt.available()==0){
         if (blocksout) client.print("\r\n");  blocksout=0;
     }
  }
}

This code on the other hand does the 64 bit encoding but it does not appear as an attachment. It just prints the values in the body of my email.

Am I understanding this wrong an attachment would not be an "attachment" but would appear in the body of the email?

This is how I send a text file attachment from my SD card.

  client.println("Subject: Arduino email image");

// this is new 
  client.write("MIME-Version: 1.0\r\n");
  client.write("Content-Type: Multipart/mixed; boundary=frontier\r\n\r\n");
 
  client.write("--frontier\r\n");
  client.write("Content-Type: text/plain\r\n\r\n");
 
  client.write("This is an inage from my Arduino!\r\n");

  client.write("--frontier\r\n");
  client.write("Content-Type: text/plain\r\n");
  client.write("Content-Disposition: attachment; filename=test.txt\r\n\r\n");

// this is where you would send your file
  int clientCount = 0;

  File myFile = SD.open("test.txt");
  
  if(myFile) {
    while(myFile.available()) {
      clientCount = myFile.read(tBuf,64);
      client.write((byte*)tBuf,clientCount);
    }
    myFile.close();                  
  }
  else {
    Serial.println(F("File open failed"));
  }

  client.write("--frontier--\r\n");

// end of new
// send a period on a line by itself
  client.println(".");

Hey Tim, that works a charm :slight_smile: on text messages. However when I tried to send an image the image is sent all smudged up:

The code is used is:

// this is new
  client.write("MIME-Version: 1.0\r\n");
  client.write("Content-Type: Multipart/mixed; boundary=frontier\r\n\r\n");
 
  client.write("--frontier\r\n");
  client.write("Content-Type: image/jpg\r\n\r\n");
 
  client.write("This is an image from my Arduino!\r\n");

  client.write("--frontier\r\n");
  client.write("Content-Type: image/jpg\r\n");
  client.write("Content-Disposition: attachment; filename=picture.jpg\r\n\r\n");

// this is where you would send your file
  int clientCount = 0;

//  File myFile = SD.open("test.txt");
  File myFile = SD.open("picture.jpg");
  if(myFile) {
    while(myFile.available()) {
      clientCount = myFile.read(tBuf,64);
      client.write((byte*)tBuf,clientCount);
    }
    myFile.close();                 
  }
  else {
    Serial.println(F("File open failed"));
  }

  client.write("--frontier--\r\n");

// end of new
// send a period on a line by itself
  client.println(".");

  if(!eRcv()) return 0;

  Serial.println(F("Sending QUIT"));
  strcpy_P(tBuf,PSTR("QUIT\r\n"));  
  client.write(tBuf);
  if(!eRcv()) return 0;

  client.stop();

  Serial.println(F("disconnected"));

Any pointers ? Thanks a ton.

Ok just wondering

  if(myFile) {
    while(myFile.available()) {
      clientCount = myFile.read(tBuf,64);
      client.write((byte*)tBuf,clientCount);
    }

If i play with the value 64 the email does not get sent even if I adjust char tBuf[64];

Can I do away with the buffer concept altogether or increase its size while sending the image?

I think you must base64 encode the image to send it as an email attachment. Don't have much time to test today, but I'll work on it when I can.

Thank you for your reply Tim. I did try the base 64 encoding using the code posted by Razorblade but was not successful. However, i did notice that when I did send images using the code you posted .txt files, the resultant files were not the same.
I opened both the original file and the attachment of the same file that i received in my email in a hex editor.
0x0D in the original were replaced by 0x0A in the attachment at various locations.

I have attached both the files.

I think you must base64 encode the image to send it as an email attachment.

Why is this usually done?

Thanks in advance.

picture.jpg

picture1.jpg