diff --git a/app/routes/api.orders.$orderId.invoice.tsx b/app/routes/api.orders.$orderId.invoice.tsx
index 1abe9c1..cc3f53c 100644
--- a/app/routes/api.orders.$orderId.invoice.tsx
+++ b/app/routes/api.orders.$orderId.invoice.tsx
@@ -3,6 +3,7 @@ import { authenticate } from "../shopify.server";
import db from "../db.server";
import { generateInvoice } from "../services/invoice/generateInvoice.server";
import { cancelAndReissue } from "../services/invoice/cancelAndReissue.server";
+import { sendInvoiceEmail } from "../services/invoice/email.server";
/**
* GET /api/orders/:orderId/invoice → returns latest invoice metadata + history
@@ -60,6 +61,50 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
return cors(Response.json({ ok: true, op, ...result }));
}
+ if (op === "send") {
+ const orderGid = orderId.startsWith("gid://")
+ ? orderId
+ : `gid://shopify/Order/${orderId}`;
+ let invoice = await db.invoice.findFirst({
+ where: {
+ shopDomain: session.shop,
+ orderId: orderGid,
+ kind: "invoice",
+ cancelledAt: null,
+ },
+ orderBy: [{ version: "desc" }, { createdAt: "desc" }],
+ });
+ if (!invoice) {
+ const generated = await generateInvoice({
+ shopDomain: session.shop,
+ admin,
+ orderId,
+ });
+ invoice = await db.invoice.findUnique({ where: { id: generated.invoiceId } });
+ }
+ if (!invoice) throw new Error("Failed to materialise an invoice for this order.");
+ const sendResult = await sendInvoiceEmail({
+ shopDomain: session.shop,
+ invoiceId: invoice.id,
+ });
+ if (!sendResult.ok) {
+ return cors(
+ Response.json(
+ { ok: false, op: "send", error: sendResult.errorMessage ?? "Email send failed." },
+ { status: 422 },
+ ),
+ );
+ }
+ return cors(
+ Response.json({
+ ok: true,
+ op: "send",
+ invoiceNumber: invoice.invoiceNumber,
+ toAddress: sendResult.toAddress,
+ }),
+ );
+ }
+
const result = await generateInvoice({
shopDomain: session.shop,
admin,
diff --git a/data/regen-RE-1001.pdf b/data/regen-RE-1001.pdf
new file mode 100644
index 0000000..7892605
Binary files /dev/null and b/data/regen-RE-1001.pdf differ
diff --git a/data/regen-RE-1002.pdf b/data/regen-RE-1002.pdf
new file mode 100644
index 0000000..d2a0219
Binary files /dev/null and b/data/regen-RE-1002.pdf differ
diff --git a/data/sample-rechnung.pdf b/data/sample-rechnung.pdf
index 31553f0..750ec80 100644
Binary files a/data/sample-rechnung.pdf and b/data/sample-rechnung.pdf differ
diff --git a/data/sample-storno.pdf b/data/sample-storno.pdf
index f0c1cfa..514d4e3 100644
Binary files a/data/sample-storno.pdf and b/data/sample-storno.pdf differ
diff --git a/extensions/invoice-order-action/src/ActionExtension.tsx b/extensions/invoice-order-action/src/ActionExtension.tsx
index f74e192..c62754a 100644
--- a/extensions/invoice-order-action/src/ActionExtension.tsx
+++ b/extensions/invoice-order-action/src/ActionExtension.tsx
@@ -13,7 +13,7 @@ function Extension() {
const orderGid: string | undefined = data?.selected?.[0]?.id;
const orderId = orderGid ? orderGid.split("/").pop() : undefined;
- async function trigger(action: "generate" | "cancel_reissue") {
+ async function trigger(action: "generate" | "cancel_reissue" | "send") {
if (!orderId) {
setError("No order selected");
return;
@@ -47,6 +47,9 @@ function Extension() {
trigger("generate")} disabled={busy}>
Generate / regenerate
+ trigger("send")} disabled={busy}>
+ Send invoice email
+
trigger("cancel_reissue")} disabled={busy}>
Cancel & reissue
diff --git a/shopify.app.toml b/shopify.app.toml
index 0dbcaf0..12c18d0 100644
--- a/shopify.app.toml
+++ b/shopify.app.toml
@@ -39,5 +39,4 @@ redirect_urls = [
]
[build]
-include_config_on_deploy = true
automatically_update_urls_on_dev = true