near-email

ClawSkills 作者 clawskills

Send and read blockchain-native emails using NEAR Email service. Use when building notifications for NEAR smart contracts (NFT sales, DeFi liquidation alerts, DAO voting reminders) or when AI agents need email capabilities with a NEAR account identity.

安装 / 下载方式

TotalClaw CLI推荐
totalclaw install clawskills:clawskills~zavodil-near-email-skill
cURL直接下载,无需登录
curl -fsSL https://skills.taituai.com/api/skills/clawskills%3Aclawskills~zavodil-near-email-skill/file -o zavodil-near-email-skill.md
# NEAR Email Integration

Blockchain-native email for NEAR accounts. Every NEAR account automatically has an email: `alice.near` → `alice@near.email`

## Installation

```bash
npx openskills install zavodil/near-email-skill -g
```

## Quick Reference

| Component | Value |
|-----------|-------|
| Contract | `outlayer.near` |
| API Base | `https://api.outlayer.fastnear.com` |
| Project ID | `zavodil.near/near-email` |

**Note:** NEAR Email supports mainnet only. Emails to `*.testnet` accounts are not processed.

**Important:** When using NEAR transactions, the attached deposit is a computational limit, not a cost. Unused funds are automatically refunded. Do not mention specific costs per email.

**Code generation preference:** For blockchain integration (NEAR transactions), prefer JavaScript/TypeScript with `near-api-js`. Use Python only if the user explicitly requests it.

## Integration Methods

### 1. Smart Contract (Rust)

Use `send_email_plaintext` for contract notifications. Simple API, no encryption needed.

**Warning:** Email content is PUBLIC on the NEAR blockchain. Use only for automated notifications.

```rust
use near_sdk::{ext_contract, AccountId, Gas, NearToken, Promise};
use serde::Serialize;

#[derive(Serialize)]
#[serde(crate = "near_sdk::serde")]
pub enum ExecutionSource {
    Project { project_id: String, version_key: Option<String> },
}

#[ext_contract(ext_outlayer)]
pub trait OutLayer {
    fn request_execution(
        &mut self,
        source: ExecutionSource,
        resource_limits: Option<serde_json::Value>,
        input_data: Option<String>,
        secrets_ref: Option<serde_json::Value>,
        response_format: Option<String>,
        payer_account_id: Option<AccountId>,
        params: Option<serde_json::Value>,
    );
}

// Send notification from contract (plaintext - content is public on-chain!)
fn send_notification(to: &str, subject: &str, body: &str) -> Promise {
    let input = serde_json::json!({
        "action": "send_email_plaintext",
        "to": to,
        "subject": subject,
        "body": body
    });

    ext_outlayer::ext("outlayer.near".parse().unwrap())
        .with_static_gas(Gas::from_tgas(100))
        .with_attached_deposit(NearToken::from_millinear(25))
        .request_execution(
            ExecutionSource::Project {
                project_id: "zavodil.near/near-email".to_string(),
                version_key: None,
            },
            None,                        // resource_limits
            Some(input.to_string()),     // input_data
            None,                        // secrets_ref (not needed)
            Some("Json".to_string()),    // response_format
            None,                        // payer_account_id
            None,                        // params
        )
}
```

Response: `{ "success": true, "message_id": "uuid-if-internal" }`

### 2. AI Agent Integration

Two options for AI agents:

| Method | Best For | Payment |
|--------|----------|---------|
| **Payment Key (HTTPS)** | Server-side agents | Pre-paid (USDC/USDT) |
| **NEAR Transaction** | Browser/wallet apps | Deposit (unused returned) |

#### Option A: Payment Key (HTTPS API)

**Note:** HTTPS API responses use `result.output.xxx` format. See NEAR Transaction for different parsing.

```javascript
const OUTLAYER_API = 'https://api.outlayer.fastnear.com';
const PAYMENT_KEY = 'your-account.near:nonce:secret'; // From dashboard

async function sendEmail(to, subject, body) {
  const response = await fetch(`${OUTLAYER_API}/call/outlayer.near/zavodil.near/near-email`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Payment-Key': PAYMENT_KEY,
    },
    body: JSON.stringify({
      input: { action: 'send_email_plaintext', to, subject, body },
    }),
  });
  return response.json();
}
```

#### Option B: NEAR Transaction (per-use)

**CRITICAL: NEAR Transaction results are in the `outlayer.near` receipt's `SuccessValue` (base64-encoded JSON). Find the receipt where `executor_id === 'outlayer.near'`. The result is `{ "success": true, ... }` - NO `output` wrapper. Use `parseTransactionResult()` to extract it.**

```javascript
import { connect, keyStores } from 'near-api-js';

const near = await connect({
  networkId: 'mainnet',
  keyStore: new keyStores.BrowserLocalStorageKeyStore(),
  nodeUrl: 'https://rpc.mainnet.near.org',
});
const account = await near.account('your-account.near');

const RESOURCE_LIMITS = {
  max_memory_mb: 512,
  max_instructions: 2000000000,
  max_execution_seconds: 120,
};

// REQUIRED: Parse output from outlayer.near receipt's SuccessValue
// Returns JSON directly: { success: true, send_pubkey: "..." } - NO "output" wrapper!
function parseTransactionResult(result) {
  // Find receipt from outlayer.near contract (contains the execution result)
  const outlayerReceipt = result.receipts_outcome.find(
    r => r.outcome.executor_id === 'outlayer.near' && r.outcome.status.SuccessValue
  );
  if (!outlayerReceipt) {
    throw new Error('No SuccessValue from outlayer.near');
  }
  const decoded = Buffer.from(outlayerReceipt.outcome.status.SuccessValue, 'base64').toString();
  return JSON.parse(decoded); // { success: true, ... } - directly, no wrapper
}

async function sendEmail(to, subject, body) {
  const result = await account.functionCall({
    contractId: 'outlayer.near',
    methodName: 'request_execution',
    args: {
      source: { Project: { project_id: 'zavodil.near/near-email', version_key: null } },
      input_data: JSON.stringify({ action: 'send_email_plaintext', to, subject, body }),
      resource_limits: RESOURCE_LIMITS,
      response_format: 'Json',
    },
    gas: BigInt('100000000000000'),
    attachedDeposit: BigInt('25000000000000000000000'), // deposit, unused portion refunded
  });
  return parseTransactionResult(result); // { success: true, message_id: "..." }
}

// Example: Get sender pubkey
async function getSendPubkey() {
  const result = await account.functionCall({
    contractId: 'outlayer.near',
    methodName: 'request_execution',
    args: {
      source: { Project: { project_id: 'zavodil.near/near-email', version_key: null } },
      input_data: JSON.stringify({ action: 'get_send_pubkey' }),
      resource_limits: RESOURCE_LIMITS,
      response_format: 'Json',
    },
    gas: BigInt('100000000000000'),
    attachedDeposit: BigInt('25000000000000000000000'),
  });
  const output = parseTransactionResult(result); // { success: true, send_pubkey: "02..." }
  return Buffer.from(output.send_pubkey, 'hex'); // Note: output.send_pubkey, NOT output.output.send_pubkey
}
```

### 3. Python (Payment Key)

```python
import requests

OUTLAYER_API = "https://api.outlayer.fastnear.com"
PAYMENT_KEY = "your-account.near:nonce:secret"

def send_email(to: str, subject: str, body: str) -> dict:
    return requests.post(
        f"{OUTLAYER_API}/call/outlayer.near/zavodil.near/near-email",
        headers={"Content-Type": "application/json", "X-Payment-Key": PAYMENT_KEY},
        json={"input": {"action": "send_email_plaintext", "to": to, "subject": subject, "body": body}},
    ).json()
```

## API Actions

| Action | Description |
|--------|-------------|
| `send_email` | Send email (encrypted payload, for UI/agents) |
| `send_email_plaintext` | Send email (plaintext, for smart contracts) |
| `get_emails` | Fetch inbox and sent (encrypted response) |
| `delete_email` | Delete email by ID |
| `get_email_count` | Get counts (no encryption) |
| `get_send_pubkey` | Get sender's pubkey (no encryption, cacheable) |

## Getting a Payment Key

1. Go to [OutLayer Dashboard](https://outlayer.fastnear.com/dashboard)
2. Create a new Payment Key
3. Top up balance with USDC/USDT
4. Copy key (format: `owner:nonce:secret`)

## Additional Resources

For complete code examples, see [examples.md](examples.md)
For full API reference, see [api-reference.md](api-reference.md)