Email Documents Scanned with Dynamic Web TWAIN

Email is a convenient way to send scanned documents. The ability to send emails is built into many network scanners. For example, HP All-in-One devices have a Scan-to-Email app.

However, we cannot use these devices’ built-in email-sending ability if we want to control the scanning process in a web scanning app based on Dynamic Web TWAIN (DWT). We have to implement our own mechanism to send emails.

There are many ways to add scan-to-email function to a web scanning app.

  1. Using a third-party service like SMTPjs. It is simple to use and does not require a backend server. However, we have to expose our password to them.
  2. Using email services’ APIs. Both Outlook and Gmail provide APIs to send emails and support large files uploading. But we have to implement the APIs for every mail service.
  3. Creating a server application which receives scanned documents and sends them using the SMTP protocol. If the documents are too large (over 10MB), then we can provide a download link.

In this article, we are going to add a scan-to-email function following the third way.

If the scanned documents are less than 10MB, the user will get such an email with attachments:

Hi there,

Your scanned documents are attached.

Or the user will get the following email with a dropbox link:

Hi there,

Your scanned documents are large. We've uploaded to dropbox: https://www.dropbox.com/s/44mbz8w1qgceb3f/20210707151820.jpg?dl=0

Technologies we are going to use:

  • Dynamic Web TWAIN to scan documents in the browser
  • Python+Flask to create the backend
  • Dropbox to store large files

Getting Started With Dynamic Web TWAIN

Environment Setup

  1. Install Python 3
  2. Install required Python packages:

     pip install Flask, dropbox
    

Implement the Email Sending Flask Backend

We choose the Python+Flask combination to create the backend.

Three Python files are created:

app.py: the Flask application
email_utils.py: functions related to emails
dropbox_helper.py: a class to upload files to dropbox and create a sharing link

In app.py, import required packages and create the Flask application.

from flask import Flask, request, render_template
import email_utils
import dropbox_helper
import os
app = Flask(__name__, static_url_path='/', static_folder='static')

Use route to bind a URL to the function which receives scanned documents and sends emails:

@app.route('/emails', methods=['POST'])
def emails():
    if request.method == 'POST':
        f = request.files['RemoteFile']
        send_to = request.form['sendto']
        filebytes=f.read()
        result=False
        if len(filebytes)>10*1024*1024: # if the file is larger than 10MB, using dropbox
            dbx = dropbox_helper.dropbox_helper()
            path = '/{}'.format(f.filename)
            if dbx.upload(filebytes,path):
                link = dbx.create_sharing_link(path)
                if link!=None:
                    result = email_utils.send_with_link(send_to,link)
        else:
            result = email_utils.send_with_attatchment(send_to,filebytes,f.filename)
        if result==True:
            result="success"
        else:
            result="failed"
        response={"status": result}
        return response

The content of the email_utils.py:

from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import smtplib

def send_with_attatchment(to,filebytes,filename):
    msg = build_message(to,filebytes=filebytes,filename=filename)
    return send(msg,to)

def send_with_link(to,link):
    msg = build_message(to,link=link)
    return send(msg,to)

def send(msg,to):
    try:
        account = get_account()
        passwd = get_passwd()
        server = smtplib.SMTP(get_smtp_server(),587)
        server.ehlo()
        server.starttls()
        server.login(account,passwd)
        server.sendmail(account, to, msg.as_string())
        server.quit()
        print("Successfully sent email")
        return True
    except smtplib.SMTPException:
        print("Failed")
        return False

def build_message(to,link=None,filebytes=None,filename=None):
    msg = MIMEMultipart()
    msg['to'] = to
    msg['from'] = get_account()
    msg['subject'] = 'Your scanned documents'
    if filebytes != None:
        att1 = MIMEText(filebytes, 'base64', 'utf8')
        att1["Content-Type"] = 'application/octet-stream'
        att1["Content-Disposition"] = 'attachment; filename="{}"'.format(filename);
        msg.attach(att1)
        body="Hi there,\n\nYour scanned documents are attached."
        msg.attach(MIMEText(body,'plain'))
    if link!=None:
        body="Hi there,\n\nYour scanned documents are large. We've uploaded to dropbox: {}".format(link)
        msg.attach(MIMEText(body,'plain'))
    return msg
    
def get_account():
    with open('account') as f:
        content = f.readlines()
        return content[0].strip()
        
def get_passwd():
    with open('account') as f:
        content = f.readlines()
        return content[1].strip()
        
def get_smtp_server():
    with open('account') as f:
        content = f.readlines()
        return content[2].strip()        


if __name__ == '__main__':
   send_with_attatchment("xx@dynamsoft.com",open('test.jpg', 'rb').read(),"test.jpg")

Account info is stored in a plain text file where the first line stores the emali account, the second line the password and the third line the smtp server address.

The content of the dropbox_helper (The Dropbox Python SDK is used):

import dropbox
from dropbox.files import WriteMode
from dropbox.exceptions import ApiError

class dropbox_helper:

    def __init__(self):
        #Create an app and generate your access token here: https://www.dropbox.com/developers/apps/
        self.dbx = dropbox.Dropbox("<access_token>")

    def upload(self,filebytes,filepath):
        try:
            self.dbx.files_upload(filebytes, filepath, mode=WriteMode('overwrite'))
            return True
        except ApiError as err:
            return False
            
    def create_sharing_link(self,filepath):
        try:
            shared_link_metadata = self.dbx.sharing_create_shared_link(filepath)
            return shared_link_metadata.url
        except ApiError as err:
            return None
        

if __name__ == '__main__':
    helper=dropbox_helper()
    if helper.upload(open('email_utils.py', 'rb').read(),'/email_utils.py'):
        print(helper.create_sharing_link('/email_utils.py'))

We can also set the password and expires but this is not available for a free account.

expires = datetime.datetime.now() + datetime.timedelta(days=30)
settings = dropbox.sharing.SharedLinkSettings(require_password=True,link_password="123456",expires=expires)
shared_link_metadata = dbx.sharing_create_shared_link_with_settings(filepath, settings=desired_shared_link_settings)

Implement the Document Scanning HTML5 Frontend

Several elements are created:

  • A button to acquire scanned documents.
  • A button to load local images.
  • A button to email scanned documents.
  • An input for users to enter their email account.
  • Radios to select output type.

HTML:

<select size="1" id="source" style="position: relative; width: 220px;"></select>
<input type="button" value="Scan" onclick="AcquireImage();" />
<input type="button" value="Load" onclick="LoadImage();" />
<br />
<label>
    <input type="radio" value="jpg" name="ImageType" id="imgTypejpeg" checked="checked" />JPEG</label>
<label>
    <input type="radio" value="tif" name="ImageType" id="imgTypetiff" />TIFF</label>
<label>
    <input type="radio" value="pdf" name="ImageType" id="imgTypepdf" />PDF</label>
<br/>
<label>To: <input type="text" id="sendto" name="sendto"></label>
<input type="button" value="Email" onclick="Email();" />

<!-- dwtcontrolContainer is the default div id for Dynamic Web TWAIN control.-->
<div id="dwtcontrolContainer"></div>

JavaScript:

Here, we use DWT’s HTTPUpload methods to upload scanned documents and use the SetHTTPFormField method to add an email form field.

 function Email(){
    // Clear old fields before adding new ones
    sendto = document.getElementById("sendto").value;
    DWObject.ClearAllHTTPFormField();
    DWObject.SetHTTPFormField("sendto", sendto);
    // Convert scanned documents with the specified file format and upload
    ConvertAndSend("emails");
}

function ConvertAndSend(target){
    if (DWObject) {
        // If no image in buffer, return the function
        if (DWObject.HowManyImagesInBuffer == 0)
            return;
        var strHTTPServer = location.hostname; //The name of the HTTP server. For example: "www.dynamsoft.com";
        var CurrentPathName = unescape(location.pathname);
        var CurrentPath = CurrentPathName.substring(0, CurrentPathName.lastIndexOf("/") + 1);
        var endPoint = CurrentPath + target;
        console.log(endPoint);
        DWObject.IfSSL = false; // Set whether SSL is used
        DWObject.HTTPPort = location.port == "" ? 80 : location.port;
        var uploadfilename = getFormattedDate(); 
        // Upload the image(s) to the server asynchronously
        if (document.getElementById("imgTypejpeg").checked == true) {
            //If the current image is B&W
            //1 is B&W, 8 is Gray, 24 is RGB
            if (DWObject.GetImageBitDepth(DWObject.CurrentImageIndexInBuffer) == 1)
                //If so, convert the image to Gray
                DWObject.ConvertToGrayScale(DWObject.CurrentImageIndexInBuffer);
            //Upload image in JPEG
            DWObject.HTTPUploadThroughPost(strHTTPServer, DWObject.CurrentImageIndexInBuffer, endPoint, uploadfilename + ".jpg", OnEmptyResponse, OnServerReturnedSomething);
        }
        else if (document.getElementById("imgTypetiff").checked == true) {
            DWObject.HTTPUploadAllThroughPostAsMultiPageTIFF(strHTTPServer, endPoint, uploadfilename + ".tif", OnEmptyResponse, OnServerReturnedSomething);
        }
        else if (document.getElementById("imgTypepdf").checked == true) {
            DWObject.HTTPUploadAllThroughPostAsPDF(strHTTPServer, endPoint, uploadfilename + ".pdf", OnEmptyResponse, OnServerReturnedSomething);
        }
    }
}

function getFormattedDate() {
    var date = new Date();
    var month = date.getMonth() + 1;
    var day = date.getDate();
    var hour = date.getHours();
    var min = date.getMinutes();
    var sec = date.getSeconds();
    month = (month < 10 ? "0" : "") + month;
    day = (day < 10 ? "0" : "") + day;
    hour = (hour < 10 ? "0" : "") + hour;
    min = (min < 10 ? "0" : "") + min;
    sec = (sec < 10 ? "0" : "") + sec;
    var str = date.getFullYear().toString() + month + day + hour + min + sec;
    return str;
}

There are two callbacks of the upload method.

We can parse the returned JSON result to examine whether the backend has sent the email in success.

function OnEmptyResponse() {
    console.log('empty response');
}
function OnServerReturnedSomething(errorCode, errorString, sHttpResponse) {
    console.log(sHttpResponse);
    var response;
    try {
        response = JSON.parse(sHttpResponse);
        if (response.status=="success"){
            alert("Success");
        }else{
        alert("Failed");
        }
    }
    catch(err) {
        console.log(err);
    }
}

Deploy to Azure Web App Service

We can deploy the app to Azure. There is a guide about how to do this: Quickstart: Create a Python app using Azure App Service on Linux (Azure portal).

Source Code

https://github.com/xulihang/Scan-to-Email