// cd /blog/webmite-gmail
Sending email through Gmail from a WebMite
The WiFi versions of the PicoMite can send email on their own: an alarm from a sensor, a daily log file, a reading from the garden. This article sets up a Gmail account to accept mail from the board, then gives complete MMBasic programs that send a message, with or without an attachment.
In short
- Gmail accepts mail from the board. The board logs in with your Gmail address and an app password: a separate 16-letter password for one device, which you can delete at any time without changing your main password.
- The firmware does the encryption.
WEB OPEN TLS CLIENTconnects tosmtp.gmail.comon port 465, which is encrypted from the first byte. Port 587 is not supported, because it needs STARTTLS, which switches to encryption partway through. - Sending a message takes under ten exchanges. Each one is a single
WEB TCP CLIENT REQUEST, and the program checks Gmail's three-digit reply before going on. - Attachments are streamed, not loaded. The file is read 57 bytes at a time, base64 encoded and sent in 4 KB blocks with
WEB TCP CLIENT WRITE, so even a Pico W can send a file bigger than its free memory.
On this page
What you need
- A Raspberry Pi Pico W or Pico 2 W running a WebMite build of the PicoMite firmware, version 6.04 or later. These builds have the TLS (secure connection) commands this needs. The current release is on GitHub.
- The board joined to your network. If it isn't yet, type
OPTION WIFI "network-name", "network-password"at the prompt. The board restarts and connects by itself from then on. - A Google account with 2-Step Verification turned on. A free Gmail account set up just for your devices is best: see Good habits.
Step 1: a Gmail app password
Gmail won't let a device log in with your normal password. Instead, you create an app password for it: a random 16-letter password for one app or device, which you can delete without affecting anything else.
- Sign in to the Google account you'll send from, at myaccount.google.com.
- Turn on 2-Step Verification if it isn't already on: Security, then 2-Step Verification. App passwords are only available with it on.
- Open myaccount.google.com/apppasswords. Type a name that tells you which device it's for, such as
WebMite greenhouse, and click Create. - Copy the password Google shows you. It appears as four groups of four letters, such as
abcd efgh ijkl mnop. Remove the spaces: the program needs the 16 letters run together. Google shows it only once; if you lose it, delete it and create another.
How the board talks to Gmail
Mail is sent with SMTP, a conversation of short text lines. The board sends a command; Gmail answers with a three-digit code and some text. A code starting with 2 or 3 means carry on, and 4 or 5 means something is wrong.
| The board sends | Gmail replies | Meaning |
|---|---|---|
| connects to port 465 | 220 smtp.gmail.com ESMTP | Hello: the firmware absorbs this greeting |
EHLO webmite | 250-smtp.gmail.com at your service | Introductions, plus a list of what Gmail supports |
AUTH LOGIN | 334 VXNlcm5hbWU6 | “Username:”, in base64 |
| Gmail address, in base64 | 334 UGFzc3dvcmQ6 | “Password:”, in base64 |
| App password, in base64 | 235 2.7.0 Accepted | Logged in |
MAIL FROM:<you@gmail.com> | 250 2.1.0 OK | Sender accepted |
RCPT TO:<them@example.com> | 250 2.1.5 OK | Recipient accepted |
DATA | 354 Go ahead | Send the message |
The message, then a line holding just . | 250 2.0.0 OK | Accepted for delivery |
Three details of the WebMite's commands shape the programs below:
- Port 465, not 587. On port 465 the connection is encrypted before anything is said, which is what
WEB OPEN TLS CLIENTdoes. Port 587 starts unencrypted and switches over with a STARTTLS command, which the firmware doesn't support. - No need to read the greeting. Gmail sends its 220 greeting the moment the secure connection is up, and the firmware discards it. The short pause after opening gives it time to arrive, so the program can send
EHLOstraight away and get EHLO's own reply. (WEB TCP CLIENT READexists for servers that make you wait for their greeting on a plain connection.) REQUESTfor commands,WRITEfor the message.WEB TCP CLIENT REQUESTsends up to 255 characters and waits for the reply, which suits the commands. But Gmail says nothing while the message itself arrives, so that goes out withWEB TCP CLIENT WRITE, which sends a LONGSTRING of any length without waiting. The final.goes withREQUEST, to collect Gmail's verdict.
Step 2: send a message
This is the whole program. Put your Gmail address and app password in the two Const lines at the top, put a real address in the SendMail line, and RUN it.
' send-mail.bas: send an email through Gmail from a WebMite
' https://mather.host/blog/webmite-gmail/
' Needs a WiFi (WebMite) build of the PicoMite firmware, V6.04 or
' later, joined to your network with OPTION WIFI, and a Gmail app
' password.
Option Explicit
Const GmailUser = "your.name@gmail.com" ' the Gmail account to send from
Const AppPassword = "abcdefghijklmnop" ' its app password: 16 letters, no spaces
Const CRLF = Chr$(13) + Chr$(10)
Dim Integer rx(512) ' Gmail's replies: a LONGSTRING of up to 4 KB
Dim Integer msg(512) ' the outgoing message: a LONGSTRING of up to 4 KB
' Optional: check that the server really is Google (see the article)
' WEB NTP
' WEB TLS CA "gmail-ca.pem"
SendMail "someone@example.com", "Hello from the WebMite", "Sent by a Pico running MMBasic."
Print "Mail sent"
End
' Send a plain-text email. The body can be up to 255 characters.
Sub SendMail(mailto As String, subject As String, body As String)
WEB OPEN TLS CLIENT "smtp.gmail.com", 465, 20000
Pause 300
Smtp "EHLO webmite", "250"
Smtp "AUTH LOGIN", "334"
Smtp B64(GmailUser), "334"
Smtp B64(AppPassword), "235"
Smtp "MAIL FROM:<" + GmailUser + ">", "250"
Smtp "RCPT TO:<" + mailto + ">", "250"
Smtp "DATA", "354"
LongString Clear msg()
LongString Append msg(), "From: " + GmailUser + CRLF
LongString Append msg(), "To: " + mailto + CRLF
LongString Append msg(), "Subject: " + subject + CRLF
LongString Append msg(), CRLF
LongString Append msg(), body + CRLF
WEB TCP CLIENT WRITE msg()
Smtp ".", "250"
WEB CLOSE TCP CLIENT
End Sub
' Send one line to Gmail and check that the reply starts with the
' expected code. On any other reply, show it and stop.
Sub Smtp(cmd As String, code As String)
WEB TCP CLIENT REQUEST cmd + CRLF, rx(), 20000
If Replied(code) = 0 Then
Print "Gmail replied: ";
LongString Print rx()
WEB CLOSE TCP CLIENT
Error "Expected " + code
EndIf
End Sub
' True if the last reply, or any line of it, starts with code
Function Replied(code As String) As Integer
If LInStr(rx(), code) = 1 Then Replied = 1
If LInStr(rx(), Chr$(10) + code) > 0 Then Replied = 1
End Function
' Base64-encode a string, as AUTH LOGIN needs
Function B64(s As String) As String
Local Integer n = Math(BASE64 ENCODE s, B64)
End Function
When it works, the console shows the firmware's connection messages and then the program's own. The address will be different:
> RUN
Connecting to 142.250.27.109 port 465 (TLS)
Connected
Mail sent
How it works:
Smtpsends one line and checks that Gmail's reply starts with the code that should come back. If it doesn't, it prints the reply and stops with an error. Gmail's replies say plainly what's wrong, so read them first: see When it goes wrong.AUTH LOGINasks for the user name and then the password, each base64 encoded.B64does this with MMBasic's built-inMATH(BASE64 ENCODE). Base64 is not encryption: the TLS connection is what keeps the password private.- The message itself is headers (
From:,To:,Subject:), a blank line, then the text. Every line ends with a carriage return and line feed, which is whatCRLFholds.
To send mail from your own program, copy in the constants, the two arrays and the four routines, then call SendMail whenever there's something to report:
If pressure < 1.0 Then SendMail "you@example.com", "Boiler alarm", "Pressure is " + Str$(pressure, 1, 2) + " bar"
Step 3: send a file
An attachment turns the message into a MIME message with several parts. The header Content-Type: multipart/mixed names a boundary string, and each part starts with a line of two dashes and that string. The first part is the text. The second is the file, with headers giving its type and name, encoded as base64 text because email can only carry text. The last boundary line has two more dashes at the end.
' send-attachment.bas: email a file through Gmail from a WebMite
' https://mather.host/blog/webmite-gmail/
' Needs a WiFi (WebMite) build of the PicoMite firmware, V6.04 or
' later, joined to your network with OPTION WIFI, and a Gmail app
' password.
Option Explicit
Const GmailUser = "your.name@gmail.com" ' the Gmail account to send from
Const AppPassword = "abcdefghijklmnop" ' its app password: 16 letters, no spaces
Const CRLF = Chr$(13) + Chr$(10)
Const Q = Chr$(34) ' a double quote
Const Boundary = "WebMite-boundary-7d3f9a" ' separates the parts of the message
Dim Integer rx(512) ' Gmail's replies: a LONGSTRING of up to 4 KB
Dim Integer msg(512) ' the outgoing message, sent in blocks of up to 4 KB
' Optional: check that the server really is Google (see the article)
' WEB NTP
' WEB TLS CA "gmail-ca.pem"
SendFile "someone@example.com", "Today's log", "The log file is attached.", "log.csv", "text/csv"
Print "Mail sent"
End
' Send a plain-text email with one file attached. fname can include a
' drive and folder, eg "B:/logs/today.csv". ftype is the file's MIME
' type, eg "text/csv", "image/jpeg" or "application/octet-stream".
Sub SendFile(mailto As String, subject As String, body As String, fname As String, ftype As String)
Local String chunk, coded, shown
Local Integer i, n
' The name shown in the email: fname without its drive or folder
shown = fname
For i = Len(fname) To 1 Step -1
If Mid$(fname, i, 1) = "/" Or Mid$(fname, i, 1) = ":" Then
shown = Mid$(fname, i + 1)
Exit For
EndIf
Next i
WEB OPEN TLS CLIENT "smtp.gmail.com", 465, 20000
Pause 300
Smtp "EHLO webmite", "250"
Smtp "AUTH LOGIN", "334"
Smtp B64(GmailUser), "334"
Smtp B64(AppPassword), "235"
Smtp "MAIL FROM:<" + GmailUser + ">", "250"
Smtp "RCPT TO:<" + mailto + ">", "250"
Smtp "DATA", "354"
' The headers, then the text part of the message
LongString Clear msg()
LongString Append msg(), "From: " + GmailUser + CRLF
LongString Append msg(), "To: " + mailto + CRLF
LongString Append msg(), "Subject: " + subject + CRLF
LongString Append msg(), "MIME-Version: 1.0" + CRLF
LongString Append msg(), "Content-Type: multipart/mixed; boundary=" + Q + Boundary + Q + CRLF
LongString Append msg(), CRLF
LongString Append msg(), "--" + Boundary + CRLF
LongString Append msg(), "Content-Type: text/plain; charset=UTF-8" + CRLF
LongString Append msg(), CRLF
LongString Append msg(), body + CRLF
' The attachment's own headers
LongString Append msg(), "--" + Boundary + CRLF
LongString Append msg(), "Content-Type: " + ftype + "; name=" + Q + shown + Q + CRLF
LongString Append msg(), "Content-Transfer-Encoding: base64" + CRLF
LongString Append msg(), "Content-Disposition: attachment; filename=" + Q + shown + Q + CRLF
LongString Append msg(), CRLF
' The file: every 57 bytes become one 76-character line of base64,
' and each block is sent as soon as it holds about 4 KB
Open fname For Input As #1
Do While Not Eof(#1)
chunk = Input$(57, #1)
n = Math(BASE64 ENCODE chunk, coded)
LongString Append msg(), coded + CRLF
If LLen(msg()) > 4000 Then
WEB TCP CLIENT WRITE msg()
LongString Clear msg()
EndIf
Loop
Close #1
' Close the last part, send what is left, then end the message
LongString Append msg(), "--" + Boundary + "--" + CRLF
WEB TCP CLIENT WRITE msg()
Smtp ".", "250"
WEB CLOSE TCP CLIENT
End Sub
' Send one line to Gmail and check that the reply starts with the
' expected code. On any other reply, show it and stop.
Sub Smtp(cmd As String, code As String)
WEB TCP CLIENT REQUEST cmd + CRLF, rx(), 20000
If Replied(code) = 0 Then
Print "Gmail replied: ";
LongString Print rx()
WEB CLOSE TCP CLIENT
Error "Expected " + code
EndIf
End Sub
' True if the last reply, or any line of it, starts with code
Function Replied(code As String) As Integer
If LInStr(rx(), code) = 1 Then Replied = 1
If LInStr(rx(), Chr$(10) + code) > 0 Then Replied = 1
End Function
' Base64-encode a string, as AUTH LOGIN needs
Function B64(s As String) As String
Local Integer n = Math(BASE64 ENCODE s, B64)
End Function
As before, fill in the constants and the SendFile line. The fourth parameter is the file to send. It can include a drive and folder, so "B:/logs/today.csv" attaches a log from the SD card. Only the name, today.csv, appears in the email.
The fifth parameter is the file's MIME type, which tells the recipient's mail program what to do with it:
| File | MIME type |
|---|---|
.txt, .bas | text/plain |
.csv | text/csv |
.jpg | image/jpeg |
.png | image/png |
.bmp | image/bmp |
| anything else | application/octet-stream |
Points to note:
- 57 bytes make one line. Base64 turns every 3 bytes into 4 characters, so 57 bytes become the 76-character lines that email expects.
- The memory used doesn't grow with the file. The lines collect in a 4 KB LONGSTRING, which is sent with
WEB TCP CLIENT WRITEand cleared as soon as it holds more than 4,000 characters. Gmail's limit is 25 MB per message, and base64 makes a file a third bigger than it is. SendFileuses file number 1. If your program keeps#1open for something else, change the three lines that use it.
Checking that it really is Google
By default, WEB OPEN TLS CLIENT encrypts the connection but doesn't check the server's certificate, so it can't tell Gmail from an impostor on your network. To make sure your app password only ever goes to Google, give the firmware Google's root certificates:
- Download Google's GTS Root R1 and GTS Root R4 certificates from Google's own repository: gtsr1.pem and gtsr4.pem. Join them into one file. On Windows, at a command prompt:
On Linux or macOS:copy /b gtsr1.pem + gtsr4.pem gmail-ca.pem
Two are needed because Gmail sends one of two certificate chains, depending on what the other end supports: an RSA chain that ends at GTS Root R1, or an ECDSA chain that ends at GTS Root R4. Both roots are valid until 2036.cat gtsr1.pem gtsr4.pem > gmail-ca.pem - Copy
gmail-ca.pemto the WebMite's A: drive, the same way you load programs: withXMODEM RECEIVE "gmail-ca.pem"and your terminal program, or by TFTP, which is on by default in the WiFi builds. - Remove the
'from the two lines near the top of the program:WEB NTP WEB TLS CA "gmail-ca.pem"
WEB NTP sets the clock, which the check needs because certificates have start and end dates. If your program already sets the clock, for example with WEB NTP and your time zone, leave that line out. WEB TLS CA applies to every later secure connection, including MQTT, until WEB TLS NOVERIFY. So if the program also talks to other secure servers, add their root certificates to the same file.
When it goes wrong
| What you see | What it means |
|---|---|
535-5.7.8 Username and Password not accepted | The Gmail address or app password is wrong. Check that the spaces are out of the app password. Gmail gives the same reply when the app password has been deleted, which includes after a change to the account's main password. |
534-5.7.9 Application-specific password required | The account's normal password was used. Create an app password (Step 1). |
Failed to convert web address | The board couldn't look up smtp.gmail.com. Check that it's on your network: PRINT MM.INFO$(IP ADDRESS) should show an address. |
No response from TLS server, or another error mentioning TLS | The secure connection didn't come up. Some networks block port 465. With certificate checking on, check that the clock was set and that gmail-ca.pem holds both certificates. |
Cannot find file | After WEB TLS CA, gmail-ca.pem isn't on the current drive. In SendFile, the file to attach isn't where the program says. |
A reply starting 5 after the final . | Gmail refused the message itself, for example because it's too big (552) or looks like spam (550). The rest of the reply says why. |
The mail is “from” your Gmail address, whatever From: says | Gmail always sends as the account that logged in, unless the other address is set up as a “Send mail as” address in Gmail's settings. |
Good habits
- Give your devices their own Gmail account. Anyone with the app password can read and send mail as that account, so keep it away from your personal mailbox.
- Never share a program with the password still in it. If one gets out, delete the app password at myaccount.google.com/apppasswords and create a new one.
- Send when something changes, not in a loop. Gmail limits how much an account can send each day, and it may suspend an account that looks like it's sending spam. An alarm when a reading crosses a limit, or one summary a day, is the right pace.
- Watch for lines that start with a dot. A line holding just
.ends the message, so a line of your text that starts with.needs a second one added in front; it's removed again on delivery. Base64 never contains a dot, so attachments are safe.
The WebMite's network commands are described in full in the PicoMite User Manual, and at the prompt with HELP WEB.