Five processing tasks moved onto Start, Grow and Scale. OCR, Envelope OCR, Document Detection, Image Enhancement and Image Upscaling were each a separate plugin, and we have switched all five on across those three Filestack plans. Usage limits are unchanged: 1000 units included and updated $0.03/overage.
This is the first of five posts, one per task, covering what each one returns and where it belongs in an application. Five Processing Tasks Are Now Included on Start, Grow and Scale lists the set with the allowances.
Envelope OCR is the one that reads a picture of an envelope and hands back who sent it and who it is going to, already labelled.
Pointed at that scan, envelope_ocr returns:
{
"recipient_address": {
"text": "Dr. Anneliese Farrokhzad\nWinterbourne Clinical Group\n882 Kestrel Hollow Drive\nApartment 14C\nAsheville, NC 28806"
},
"recipient_name": "Dr. Anneliese Farrokhzad",
"sender": "Marisol Okonkwo-Reyes\nNorthgate Provisioning Co.\n4127 Delancey Row, Suite 210\nPortland, OR 97219"
}
There is no coordinate work in front of that and no parsing behind it. The sender is separated from the recipient, the recipient’s name is split out from their address, and the postage box and routing barcode are gone. For a mailroom pipeline that is the whole extraction step, and it is one path segment. If you are building the wider pipeline around it, How to Pull Structured Data from Documents Using a Data Extraction SDK covers the stages either side.
Key takeaways
- The
envelope_ocrtask returnssender,recipient_nameandrecipient_address.text, so no coordinate handling is needed. - We now include Envelope OCR on the Filestack Start, Grow and Scale plans at 1,000 envelopes a month, alongside OCR, Document Detection, Image Enhancement and Image Upscaling.
- Intelligence tasks need a signed policy, and an unsigned request returns HTTP 403 naming the task that required it.
- Running document detection before
envelope_ocrcollapses the recipient address, so send the original scan. - A file that is not an envelope returns HTTP 200 with empty strings, so branch on the field rather than the status code.
How to call the envelope_ocr API
The task takes no parameters. It goes in front of the handle:
https://cdn.filestackcontent.com/security=policy:POLICY,signature:SIGNATURE/envelope_ocr/HANDLE
With application security enabled, Intelligence tasks need a signed policy. An unsigned request then returns a response naming the task that wanted one:
HTTP 403
security required for tasks: envelope_ocr
The policy needs read and convert, and it is a base64 JSON object with an HMAC-SHA256 signature over it:
import base64, hashlib, hmac, json, os, time
import requests
APP_SECRET = os.environ["FILESTACK_APP_SECRET"]
handle = "YOUR_HANDLE"
policy = json.dumps({
"call": ["read", "convert"],
"expiry": int(time.time()) + 300,
"handle": handle,
}, separators=(",", ":"))
encoded = base64.urlsafe_b64encode(policy.encode()).decode()
signature = hmac.new(APP_SECRET.encode(), encoded.encode(), hashlib.sha256).hexdigest()
url = (
"https://cdn.filestackcontent.com/"
f"security=policy:{encoded},signature:{signature}"
f"/envelope_ocr/{handle}"
)
data = requests.get(url).json()
requests is the only dependency, and FILESTACK_APP_SECRET comes from the Security section of your application in the developer portal.
Scoping the policy to one handle, as above, keeps a leaked URL useless for anything else. The full grammar, including the calls you can grant and how expiry is enforced, is in Security Policies.
The included allowance is 1,000 envelopes a month, and usage above that runs at $0.03 an envelope.
What envelope_ocr returns compared with plain OCR
Running ocr on the same envelope returns four text areas rather than three fields. Each area carries its own concatenated text:
[area 0] "Marisol Okonkwo-Reyes\nNorthgate Provisioning Co.\n4127 Delancey Row, Suite 210\nPortland, OR 97219"
[area 1] "Dr. Anneliese Farrokhzad\nWinterbourne Clinical Group\n882 Kestrel Hollow Drive\nApartment 14C\nAsheville, NC 28806"
[area 2] "||||||||||||||"
[area 3] "PLACE\nPOSTAGE\nHERE"
The grouping is already done, so the difference is not how much text handling you avoid. It is that these four blocks arrive in positional order and nothing labels them. Each carries a four-point bounding box, and those boxes are all you have to tell them apart.
To get a sender out of this you decide that area 0 is the sender because it sits top left, that area 1 is the recipient because it sits lower and further right, and that areas 2 and 3 are furniture. That decision is a heuristic about envelope layout, and it holds until an envelope arrives with a franking mark where the return address usually goes.
envelope_ocr makes the same distinction as a property name. Nothing downstream depends on where the blocks landed. The general task and its full response shape, including the per-word bounding boxes, are documented under Optical Character Recognition.
Both responses carry the same text. The difference is what names it:
| ocr returns | envelope_ocr returns | Content |
|---|---|---|
text_areas[0].text |
sender |
Marisol Okonkwo-Reyes, Northgate Provisioning Co., 4127 Delancey Row Suite 210, Portland OR 97219 |
text_areas[1].text |
recipient_address.text |
Dr. Anneliese Farrokhzad, Winterbourne Clinical Group, 882 Kestrel Hollow Drive, Apartment 14C, Asheville NC 28806 |
part of text_areas[1] |
recipient_name |
Dr. Anneliese Farrokhzad |
text_areas[2].text |
not present | ││││││││││││││, the routing barcode |
text_areas[3].text |
not present | PLACE POSTAGE HERE |
The left column is a position. The right column is a name.
Does envelope_ocr read handwriting and rotated scans
Handwriting and a small rotation do not need correcting first. A cursive envelope scanned about two and a half degrees off square returned all three fields complete.
Why not to run document detection before envelope_ocr
Preprocessing the scan first can cost you the result. On the rotated envelope above, doc_detection/envelope_ocr returned:
{
"recipient_address": { "text": "Cedar Falls, IA 50613" },
"recipient_name": "Cedar Falls, IA 50613",
"sender": "Teodoro Blanchard-Nkemelu\n17 Fernbrook Lane\nGalway Springs, VT 05452"
}
The recipient collapsed from three lines to one, and recipient_name became a city and a postcode. Document detection deskews, crops and binarises, which is useful before archiving a scan and costly in front of this task. Pass the original handle to envelope_ocr.
What envelope_ocr returns when the file is not an envelope
When the task finds no envelope in the file, the response is HTTP 200 with empty strings:
{ "recipient_address": { "text": "" }, "recipient_name": "", "sender": "" }
A page of text, a photograph, or an envelope scanned face down all land here. Branch on the field rather than on the status code:
data = requests.get(url).json()
if not data["recipient_name"] and not data["sender"]:
queue_for_manual_review(handle)
else:
save_mail_record(handle, data)
Treating a 200 with empty fields as a success is how blank rows reach a mail table.
Where envelope_ocr belongs in an upload flow
Envelope OCR is a Processing API task, not a Workflow task. It runs as a call on the handle once the upload has finished, rather than as a step attached to the upload:
npm install filestack-js
import * as filestack from 'filestack-js';
const client = filestack.init(YOUR_API_KEY);
client.picker({
onUploadDone: async ({ filesUploaded }) => {
const { handle } = filesUploaded[0];
const res = await fetch('/api/envelopes', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ handle }),
});
return res.json();
},
}).open();
Your /api/envelopes route signs the policy with the application secret, calls the task, and writes the result. Signing has to happen server side, because the secret is what makes the signature meaningful.
What Envelope OCR costs and why to store the result
The response carries Cache-Control: private, and repeat requests to the same URL are cache misses, so every call runs the task again and consumes a unit. Above the included 1,000 a month, that is $0.03 each.
Store sender, recipient_name and recipient_address.text against the handle when the mail record is created, and read them from your own table afterwards. A screen that renders a recipient on every page view should never be reaching the CDN to get it.
Is recipient_address a validated address
recipient_address.text is what was written on the envelope. It has not been checked against a postal database, standardised to a delivery point, or confirmed to exist. Deliverability, unit number handling and postcode correction belong to an address validation step after this one.
Sorting, routing and search all work on the raw text. Anything that puts a parcel on a van needs the validation pass in between.
Which applications use envelope OCR
Mail digitisation is the case this was built around. Earth Class Mail runs physical mail through a scanner and turns it into something a business can search, forward and act on, which is described in the Earth Class Mail case study. Returns intake works the same way, using the sender block to match a parcel back to an order. So does moving paper correspondence into a CRM, where the recipient name decides the owner.
The shape is the same in all three. envelope_ocr gives you one task and three fields, and the routing decision happens on a name instead of on a bounding box.
FAQ
Do I need a signed policy for every envelope_ocr call?
Yes, with application security enabled. Intelligence tasks require one, and an unsigned request returns HTTP 403 naming the task that wanted it. Scope the policy to a single handle so a leaked URL is useless for anything else.
Should I deskew or crop the scan first?
No. Running doc_detection in front of envelope_ocr collapsed the recipient address to a single line every time it was tested. Small rotations and handwriting are handled without preprocessing, so pass the original handle.
How do I detect a file that is not an envelope?
Check the fields, not the status. A non-envelope returns HTTP 200 with empty strings for all three, so a handler that branches on the status code will write blank rows into your mail table.
Can I use recipient_address for shipping?
Not on its own. It is what was written on the envelope, with no check against a postal database and no standardisation. Sorting and search work on the raw text; anything that puts a parcel on a van needs an address validation step in between.
Joshua is a web developer with over 4 years of experience building responsive, high-performance websites and web applications. Currently working as an AI Automation Specialist, he combines modern web development with automation to create efficient, scalable digital solutions. He shares practical insights on WordPress, web development, and emerging technologies.
Read More →