If you ship with Correos Express and run Odoo you have two options: install the OCA module delivery_correos_express, free and AGPL-3.0, or pay for a maintained connector. This article compares source code: the OCA module on branch 19.0 of the OCA/delivery-carrier repository and FlexigoTech's own connector, field by field and call by call.
In short: for your own shop with one contract, one fixed service and home delivery, the OCA module is enough. It stops being enough when you need scheduled pickups, pick-up point delivery, per-package weight, or a temporary API error not to leave the picking validated with no label.
What you need before you start
Three things, and no module gives you any of them. They are identical whichever route you take.
- A contract with Correos Express. Both modules talk with your credentials and your rates; neither resells shipping.
- Four values. User, password, customer code and sender code. In the OCA module they are the fields
correos_express_username,correos_express_password,correos_express_customer_codeandcorreos_express_sender_code. The sender code travels insolicitantewhen registering the shipment; the customer code incodRteand inkeyCliwhen requesting the label. - Dependencies. The OCA module needs
delivery_package_numberanddelivery_statefrom the same repository, plus the Python libraryunidecode. On Odoo.sh,unidecodegoes in the repositoryrequirements.txtor the install fails.
One routing detail: the OCA module picks the environment with the standard prod_environment flag on delivery.carrier and points to www.test.cexpr.es/wsps/ for testing and www.cexpr.es/wspsc/ for production. The prefix is not the same in both environments. Our connector uses /wspsc/ in both. Both prefixes answer on the test host, so if something fails there, start with the certificate and the credentials.
Feature by feature: what each one does
Every row comes from reading both modules, not from a brochure. Where the answer is "neither", that is written too.
| Feature | OCA module delivery_correos_express | FlexigoTech connector |
|---|---|---|
| Licence and cost | AGPL-3.0, no licence cost | OPL-1, paid module |
| Odoo versions | Branches 14.0 to 19.0 of the repo | 17, 18 and 19 |
| Dependencies | delivery_package_number, delivery_state and unidecode | delivery, stock, sale, sale_stock (all core Odoo) |
| Label format | PDF and ZPL (API types 1 and 2) | PDF (type 5, thermal) and ZPL; the dropdown also offers EPL, which travels with the same type as ZPL |
| Multi-package | Sends numBultos from number_of_packages, but every package line goes with empty weight and dimensions | Per-package weight plus height, length and width when the package has them |
| Cash on delivery | No: the reembolso field is sent empty | Neither: also sent empty |
| Scheduled pickup | Not called at all | Create, modify, cancel, cut-off time and pickup tracking, plus creaRecogida when registering the shipment |
| Pick-up point (PUDO) | No: codDirecDestino is always sent empty and there is no point lookup | Point lookup by radius and B-PACK service with a mandatory idPuntoExterno |
| Manifest / end of day | Not generated | Not as a document either; there are methods for shipment listing and report generation, but the daily close still happens in the carrier portal |
| Shipment cancellation | Posts a chatter message saying there is no cancel method | Calls the stop-delivery service and, if the contract lacks it, records the cancellation as manual |
| Tracking coming back | Yes: writes state and history on the picking, driven by the daily delivery_state cron | Yes: own cron every 30 minutes with a configurable day window, 21 by default |
| Retry on API error | No: any failure raises a UserError and stops there | Cron every 5 minutes, backoff from 5 to 240 minutes and a maximum of 6 attempts per picking |
| Rate calculation | The API does not provide it: it returns the delivery product list price and says so in writing | Neither: it returns the fixed price configured on the carrier and writes zero as the shipment exact price |
| Distinct API calls | 3: register shipment, label and tracking | 21 declared endpoints: registration, label and 19 further services, tracking included |
| Tests inside the module | 18 | 54 |
Two rows tend to surprise people. The first is cash on delivery: neither of them sends it, so if you charge on delivery, today that is handled outside Odoo with both modules. The second is the rate: the Correos Express API does not quote shipments, so neither module computes anything. The OCA one returns the delivery product list price and leaves the warning in writing; our connector returns the carrier's fixed price. If you need the cart to compute shipping, that is solved with pricing rules in Odoo or with the OCA module delivery_price_method, not with the connector.
Where the OCA module is enough
Own shop, one contract, home delivery
You sell on your own site, everything ships on the same contracted product and you do not offer pick-up point delivery. You print on a Zebra with ZPL or on a laser printer with PDF. The OCA module covers that whole flow: shipment registration, tracking number on the picking, label attached in the chatter and status updated by the cron. A paid module will do nothing better here.
Odoo 14, 15 or 16
We maintain 17, 18 and 19 and will not pretend otherwise. The OCA repository has branches for 14.0, 15.0 and 16.0. If your Odoo is from those years, the OCA module is not the cheap option: it is the option.
Multi-package where every package is alike
If you are invoiced on the total shipment weight, the package list travelling with blank kilos changes nothing for you: the total weight does travel. The problem only appears when someone needs weight per package.
You have someone who touches code
The OCA module is short and readable: around 450 lines across carrier, requests and picking. If someone in-house reads Python, patching a field or porting the branch is an afternoon of work and the control is yours.
Where it falls short and what that costs in manual work
No opinion here: this is what the code does not do, and therefore what someone does by hand.
- Pick-ups are requested outside. The OCA module calls no pick-up service: the daily notice to the carrier goes through the portal, the phone or an already agreed fixed route.
- You cannot sell pick-up point delivery from Odoo. The destination point field is sent empty and there is no point lookup, so an order with pick-up point delivery leaves the picking flow.
- Cancelling is a message, not an action. The module writes in the chatter that Correos Express offers no method to cancel a registered shipment. That is true for the registration call; the stop-delivery service exists separately and has to be enabled in your contract.
- An API outage leaves the picking half done. Any exception surfaces as a user error and there is no retry. With a few shipments a day someone presses the button again; when there are hundreds, someone has to hunt them down one by one.
- The reprint-label button only shows on a validated picking. It is conditioned on the state being done and on a tracking number existing. If your warehouse prints before validating, the button is not where they look for it.
The test certificate: the most repeated incident
Before switching modules over a connection error, check this. When calling the test environment www.test.cexpr.es, Python fails with CERTIFICATE_VERIFY_FAILED. It is not the module. That host does serve the full chain, but issued by Correos' own CA: the server certificate is signed by AC SUB 2 CORREOS and that signature goes up to AC RAIZ CORREOS, a self-signed root that is not in the public CA store. Python trusts only that store, so it stops there. Production, www.cexpr.es, validates against public CAs with nothing to change.
You can check it in ten seconds with openssl s_client -connect www.test.cexpr.es:443: the chain it returns ends in a self-signed Correos root. The quick way out is verify=False, and it is a bad idea: it weakens every HTTPS call on that instance. The right fix is a PEM bundle holding that root, passed as verify only on that carrier's calls, so production and every other integration keep verifying against the default store. The step by step is in the Correos Express SSL error in Odoo.
How to check it actually works
The test is not that the module installs. It is generating a real label against your contract and seeing the number written on the picking. Four things have to be there:
- The shipment number in the picking's tracking field, not only inside the PDF.
- The label stored as an attachment, with the number in the file name.
- The public tracking link resolving with that same number.
- The package codes, one per package you declared.

CENT/OUT/00136 after registration: flow state "Label OK", shipment number 1823059046850141, package code and the label PDF attached. If your test does not end on a screen like this, it is not working: it is compiling.Verdict
Start with the OCA module. It is free, it exists on your version's branch, and the scenario it covers is the one most shops shipping with Correos Express are in. Install it, produce a real label and work with it for a couple of weeks.
After that, write down what you still do outside Odoo. If that list includes requesting pickups, offering pick-up points, chasing pickings with no label or declaring per-package weight, that is where a paid module earns its place: not by having more buttons, but by emptying that list. If the list is empty, you are done and you do not need to buy anything.
Who sets it up is a separate conversation: installation, credentials and the certificate bundle cost the same with the free module as with the paid one. We cover that in OCA module rollout and in logistics in Odoo.
Where to go next
Frequently asked questions
Is there a free Correos Express module for Odoo?
Yes: delivery_correos_express, in the OCA/delivery-carrier repository under AGPL-3.0, with branches from 14.0 to 19.0. It covers shipment registration, PDF or ZPL labels and status updates.
Does the OCA module produce thermal printer labels?
Yes, in ZPL. The label type is a dropdown with two options, PDF and ZPL, which are types 1 and 2 of the Correos Express API. If your printer speaks another language, the module does not get there.
Can I offer pick-up point delivery with the OCA module?
Not from Odoo. The destination point field is always sent empty and the module includes no point lookup, so an order with pick-up point delivery is handled outside the picking.
Why does shipping show as 0 € on the order?
Because the Correos Express API does not quote shipments. The OCA module says so explicitly and returns the delivery product list price; our connector returns the carrier's fixed price and writes zero as the shipment exact price. Shipping cost is set with pricing rules in Odoo or with the OCA module delivery_price_method.
Can a registered shipment be cancelled?
Shipment registration has no cancellation: the OCA module writes that in the chatter and stops there. There is a separate stop-delivery service, which must be enabled in your contract, and our connector does call it.
What we do about this
Want someone to look at your specific case?
Contract, Odoo version, volume and what you do outside the ERP today. That is enough to tell whether the OCA module is all you need, and we say so even when the answer is yes.

