Hands-on walkthrough to get OpenFatture running in 15 minutes.
# Install uv (if it is not already available)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Clone the repository
git clone https://github.com/gianlucamazza/openfatture.git
cd openfatture
# Install dependencies
uv sync
# Verify the installation
uv run python -c "from openfatture import __version__; print(f'OpenFatture v{__version__}')"
.env File# Copy the template
cp .env.example .env
# Edit with your favourite editor
nano .env
# or
code .env
Update .env with your details:
# ==========================================
# COMPANY DETAILS (REQUIRED)
# ==========================================
CEDENTE_DENOMINAZIONE=Your Company SRL
CEDENTE_PARTITA_IVA=12345678901
CEDENTE_CODICE_FISCALE=12345678901
CEDENTE_INDIRIZZO=Via Roma 123
CEDENTE_CAP=00100
CEDENTE_COMUNE=Rome
CEDENTE_PROVINCIA=RM
CEDENTE_EMAIL=info@yourcompany.it
# Tax regime
# RF01 = Ordinary regime
# RF19 = Flat-tax (5%)
CEDENTE_REGIME_FISCALE=RF19
# ==========================================
# PEC (REQUIRED for SDI delivery)
# ==========================================
PEC_ADDRESS=yourcompany@pec.it
PEC_PASSWORD=your_pec_password
# SMTP server for your PEC provider
# Aruba: smtp.pec.aruba.it
# Register: smtps.pec.register.it
PEC_SMTP_SERVER=smtp.pec.aruba.it
PEC_SMTP_PORT=465
# ==========================================
# EMAIL NOTIFICATIONS (REQUIRED)
# ==========================================
NOTIFICATION_EMAIL=admin@yourcompany.it
NOTIFICATION_ENABLED=true
LOCALE=it
uv run python -c "
from openfatture.storage.database.session import init_db
init_db()
print('Database initialised!')
"
Before issuing invoices, confirm the PEC credentials work:
# Using uv
uv run python -c "
from openfatture.platform.config import get_settings
from openfatture.platform.email.sender import TemplatePECSender
settings = get_settings()
sender = TemplatePECSender(settings=settings)
print('Sending PEC test email...')
success, error = sender.send_test_email()
if success:
print('PEC configured correctly!')
print(f' Check the inbox: {settings.notification_email}')
else:
print(f'Error: {error}')
print(' Double-check PEC credentials in .env')
"
If the test email lands in your inbox you are ready to go!
# save as: create_customer.py
from openfatture.storage.database.models import Cliente
from openfatture.storage.database.session import get_session
# Initialise the database session
session = next(get_session())
# Create a new customer
cliente = Cliente(
denominazione="Acme Corporation SRL",
partita_iva="98765432100",
codice_fiscale="98765432100",
codice_destinatario="ABCDEFG", # Codice SDI del cliente
indirizzo="Via Milano 1",
cap="20100",
comune="Milano",
provincia="MI",
nazione="IT",
email="amministrazione@acme.it",
)
session.add(cliente)
session.commit()
print(f"Customer created: {cliente.denominazione} (ID: {cliente.id})")
Run:
uv run python create_customer.py
# save as: create_invoice.py
from datetime import date
from decimal import Decimal
from openfatture.storage.database.models import Cliente, Fattura, LineaFattura, StatoFattura
from openfatture.storage.database.session import get_session
session = next(get_session())
# Retrieve the customer (use the ID printed earlier)
cliente = session.query(Cliente).filter_by(id=1).first()
# Create the invoice
fattura = Fattura(
numero="001",
anno=2025,
data_emissione=date.today(),
cliente_id=cliente.id,
cliente=cliente,
stato=StatoFattura.DA_INVIARE,
imponibile=Decimal("0"),
iva=Decimal("0"),
totale=Decimal("0"),
)
# Aggiungi linea fattura
linea = LineaFattura(
numero_linea=1,
descrizione="Consulenza sviluppo software",
quantita=Decimal("10.0"),
unita_misura="ore",
prezzo_unitario=Decimal("50.00"),
aliquota_iva=Decimal("22.00"),
)
# Calculate line totals
linea.imponibile = linea.quantita * linea.prezzo_unitario # 500.00
linea.iva = linea.imponibile * (linea.aliquota_iva / 100) # 110.00
linea.totale = linea.imponibile + linea.iva # 610.00
# Attach the line to the invoice
fattura.linee = [linea]
# Recalculate invoice totals
fattura.imponibile = sum(l.imponibile for l in fattura.linee)
fattura.iva = sum(l.iva for l in fattura.linee)
fattura.totale = sum(l.totale for l in fattura.linee)
session.add(fattura)
session.commit()
print(f"Invoice created: {fattura.numero}/{fattura.anno}")
print(f" Customer: {fattura.cliente.denominazione}")
print(f" Total: €{fattura.totale}")
Run:
uv run python create_invoice.py
# save as: generate_xml.py
from pathlib import Path
from openfatture.storage.database.models import Fattura
from openfatture.storage.database.session import get_session
from openfatture.billing.xml.generator import FatturaXMLGenerator
session = next(get_session())
# Retrieve the invoice
fattura = session.query(Fattura).filter_by(numero="001", anno=2025).first()
# Generate the XML
generator = FatturaXMLGenerator(fattura)
xml_tree = generator.generate()
# Save the XML
xml_filename = f"IT{fattura.cliente.partita_iva}_{int(fattura.numero):05d}.xml"
xml_path = Path(f"/tmp/{xml_filename}")
xml_tree.write(str(xml_path), encoding="utf-8", xml_declaration=True)
print(f"XML generato: {xml_path}")
print(f" Dimensione: {xml_path.stat().st_size} bytes")
# Display a preview (for debugging only)
print(f"\nXML content:")
print(xml_tree.read_text())
Run:
uv run python generate_xml.py
# save as: send_to_sdi.py
from pathlib import Path
from openfatture.storage.database.models import Fattura
from openfatture.storage.database.session import get_session
from openfatture.platform.config import get_settings
from openfatture.platform.email.sender import TemplatePECSender
session = next(get_session())
settings = get_settings()
# Retrieve the invoice
fattura = session.query(Fattura).filter_by(numero="001", anno=2025).first()
# XML path generated earlier
xml_filename = f"IT{fattura.cliente.partita_iva}_{int(fattura.numero):05d}.xml"
xml_path = Path(f"/tmp/{xml_filename}")
# Send using the professional template
sender = TemplatePECSender(settings=settings)
print(f"Sending invoice {fattura.numero}/{fattura.anno} to SDI...")
success, error = sender.send_invoice_to_sdi(
fattura=fattura,
xml_path=xml_path,
signed=False, # Set to True if the XML was digitally signed
)
if success:
print("Invoice delivered successfully!")
print(f" Status: {fattura.stato.value}")
print(" Email sent using the professional template")
print(f" Recipient: {settings.sdi_pec_address}")
# The invoice status is now INVIATA
session.commit()
else:
print(f"Delivery error: {error}")
Run:
uv run python send_to_sdi.py
Behind the scenes:
INVIATAWhen SDI replies (typically within five days), OpenFatture sends you automatic updates via email.
| Code | Description | Automatic Email |
|---|---|---|
| AT | Transmission receipt | Email sent |
| RC | Delivery receipt | Email sent |
| NS | Rejection notice | Email sent with errors |
| MC | Failed delivery | Email sent |
| NE | Outcome notice (accepted/rejected) | /Email sent |
If you download PEC notifications manually:
# save as: process_notification.py
from pathlib import Path
from openfatture.sdi.notifiche.processor import NotificationProcessor
from openfatture.storage.database.session import get_session
from openfatture.platform.config import get_settings
from openfatture.platform.email.sender import TemplatePECSender
session = next(get_session())
settings = get_settings()
# Configure automatic emails for notifications
sender = TemplatePECSender(settings=settings)
processor = NotificationProcessor(
db_session=session,
email_sender=sender, # Enables automatic emails!
)
# Process the SDI notification file
notification_file = Path("RC_IT12345678901_00001.xml")
success, error, notification = processor.process_file(notification_file)
if success:
print(f"Notification processed: {notification.tipo_notifica.value}")
print(f" Invoice: {notification.fattura.numero}/{notification.fattura.anno}")
print(f" New status: {notification.fattura.stato.value}")
print(f"Automatic email sent to: {settings.notification_email}")
else:
print(f"Errore: {error}")
# save as: preview_email.py
from pathlib import Path
from datetime import date
from decimal import Decimal
from openfatture.storage.database.models import Cliente, Fattura
from openfatture.platform.config import get_settings
from openfatture.platform.email.renderer import TemplateRenderer
from openfatture.platform.email.models import FatturaInvioContext
settings = get_settings()
renderer = TemplateRenderer(settings=settings, locale="it")
# Mock data for the preview
cliente = Cliente(denominazione="Cliente Test SRL", partita_iva="12345678901")
fattura = Fattura(
numero="001",
anno=2025,
data_emissione=date.today(),
cliente=cliente,
totale=Decimal("610.00"),
)
# Build the template context
context = FatturaInvioContext(
fattura=fattura,
cedente={
"denominazione": settings.cedente_denominazione,
"partita_iva": settings.cedente_partita_iva,
"indirizzo": settings.cedente_indirizzo,
"cap": settings.cedente_cap,
"comune": settings.cedente_comune,
},
destinatario="sdi01@pec.fatturapa.it",
is_signed=False,
xml_filename="IT12345678901_00001.xml",
)
# Genera anteprima HTML
preview_path = renderer.preview(
template_name="sdi/invio_fattura.html",
context=context,
output_path=Path("/tmp/email_preview.html"),
)
print(f"Anteprima generata: file://{preview_path}")
print(" Open the file in your browser to review the email")
In the .env file:
EMAIL_LOGO_URL=https://tuosito.com/logo.png
EMAIL_PRIMARY_COLOR=#FF5722 # Orange
EMAIL_SECONDARY_COLOR=#212121 # Dark grey
EMAIL_FOOTER_TEXT=© 2025 My Company - VAT 12345678901
Restart the application to apply the changes.
# 1. PEC test
uv run python -c "
from openfatture.platform.config import get_settings
from openfatture.platform.email.sender import TemplatePECSender
sender = TemplatePECSender(settings=get_settings())
success, _ = sender.send_test_email()
print('PEC OK' if success else 'PEC ERROR')
"
# 2. Database test
uv run python -c "
from openfatture.storage.database.session import get_session
from openfatture.storage.database.models import Cliente
session = next(get_session())
count = session.query(Cliente).count()
print(f'Database OK ({count} customers)')
"
# 3. Configuration test
uv run python -c "
from openfatture.platform.config import get_settings
s = get_settings()
print(f'Cedente: {s.cedente_denominazione}')
print(f'PEC: {s.pec_address}')
print(f'Notifiche: {s.notification_email}')
"
Now that OpenFatture is configured:
examples/email_templates_example.pydocs/EMAIL_TEMPLATES.mddocs/CONFIGURATION.md# Check credentials
uv run python -c "
from openfatture.platform.config import get_settings
s = get_settings()
print(f'PEC: {s.pec_address}')
print(f'SMTP: {s.pec_smtp_server}:{s.pec_smtp_port}')
print(f'Password set: {\"Yes\" if s.pec_password else \"No\"}')
"
Common fixes:
# Recreate the database
uv run python -c "
from openfatture.storage.database.session import init_db
init_db()
print('Database ricreato')
"
# Inspect available templates
ls -la openfatture/platform/email/templates/
send_test_email() before sending real invoices.cp openfatture.db openfatture.db.backup.NOTIFICATION_EMAIL daily to catch SDI updates.Congratulations! OpenFatture is now configured.
For questions and support: GitHub Issues