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.
- 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.
- 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.
- 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
This article is Part 2 in a 3-Part Series.
Getting Started With Dynamic Web TWAIN
Environment Setup
- Install Python 3
-
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).